├── .gitignore ├── package-lock.json ├── package.json ├── public ├── css │ ├── codemirror │ │ ├── codemirror.css │ │ ├── dracula.css │ │ └── show-hint.css │ ├── home.css │ ├── liveBoard.css │ └── room.css ├── glimpse.png ├── icons │ ├── brush.png │ ├── clear.png │ ├── color-palette.png │ ├── eraser.png │ ├── redo.png │ └── undo.png └── js │ ├── AgoraRTC_N-4.8.1.js │ ├── codemirror │ ├── clike.js │ ├── codemirror.js │ ├── go.js │ ├── javascript-hint.js │ ├── javascript.js │ ├── python.js │ └── show-hint.js │ ├── home.js │ ├── liveBoard.js │ └── room.js ├── readme.md ├── server.js └── views ├── home.ejs └── room.ejs /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | .env -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "CollabCode", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "devStart": "nodemon server.js" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "dependencies": { 13 | "agora-access-token": "^2.0.4", 14 | "ejs": "^3.1.6", 15 | "express": "^4.17.2", 16 | "socket.io": "^4.4.1", 17 | "uuid": "^8.3.2" 18 | }, 19 | "devDependencies": { 20 | "dotenv": "^16.0.0", 21 | "nodemon": "^2.0.15" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /public/css/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 | direction: ltr; 9 | } 10 | 11 | /* PADDING */ 12 | 13 | .CodeMirror-lines { 14 | padding: 4px 0; /* Vertical padding around content */ 15 | } 16 | .CodeMirror pre.CodeMirror-line, 17 | .CodeMirror pre.CodeMirror-line-like { 18 | padding: 0 4px; /* Horizontal padding of content */ 19 | } 20 | 21 | .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler { 22 | background-color: white; /* The little square between H and V scrollbars */ 23 | } 24 | 25 | /* GUTTER */ 26 | 27 | .CodeMirror-gutters { 28 | border-right: 1px solid #ddd; 29 | background-color: #f7f7f7; 30 | white-space: nowrap; 31 | } 32 | .CodeMirror-linenumbers {} 33 | .CodeMirror-linenumber { 34 | padding: 0 3px 0 5px; 35 | min-width: 20px; 36 | text-align: right; 37 | color: #999; 38 | white-space: nowrap; 39 | } 40 | 41 | .CodeMirror-guttermarker { color: black; } 42 | .CodeMirror-guttermarker-subtle { color: #999; } 43 | 44 | /* CURSOR */ 45 | 46 | .CodeMirror-cursor { 47 | border-left: 1px solid black; 48 | border-right: none; 49 | width: 0; 50 | } 51 | /* Shown when moving in bi-directional text */ 52 | .CodeMirror div.CodeMirror-secondarycursor { 53 | border-left: 1px solid silver; 54 | } 55 | .cm-fat-cursor .CodeMirror-cursor { 56 | width: auto; 57 | border: 0 !important; 58 | background: #7e7; 59 | } 60 | .cm-fat-cursor div.CodeMirror-cursors { 61 | z-index: 1; 62 | } 63 | .cm-fat-cursor .CodeMirror-line::selection, 64 | .cm-fat-cursor .CodeMirror-line > span::selection, 65 | .cm-fat-cursor .CodeMirror-line > span > span::selection { background: transparent; } 66 | .cm-fat-cursor .CodeMirror-line::-moz-selection, 67 | .cm-fat-cursor .CodeMirror-line > span::-moz-selection, 68 | .cm-fat-cursor .CodeMirror-line > span > span::-moz-selection { background: transparent; } 69 | .cm-fat-cursor { caret-color: transparent; } 70 | @-moz-keyframes blink { 71 | 0% {} 72 | 50% { background-color: transparent; } 73 | 100% {} 74 | } 75 | @-webkit-keyframes blink { 76 | 0% {} 77 | 50% { background-color: transparent; } 78 | 100% {} 79 | } 80 | @keyframes blink { 81 | 0% {} 82 | 50% { background-color: transparent; } 83 | 100% {} 84 | } 85 | 86 | /* Can style cursor different in overwrite (non-insert) mode */ 87 | .CodeMirror-overwrite .CodeMirror-cursor {} 88 | 89 | .cm-tab { display: inline-block; text-decoration: inherit; } 90 | 91 | .CodeMirror-rulers { 92 | position: absolute; 93 | left: 0; right: 0; top: -50px; bottom: 0; 94 | overflow: hidden; 95 | } 96 | .CodeMirror-ruler { 97 | border-left: 1px solid #ccc; 98 | top: 0; bottom: 0; 99 | position: absolute; 100 | } 101 | 102 | /* DEFAULT THEME */ 103 | 104 | .cm-s-default .cm-header {color: blue;} 105 | .cm-s-default .cm-quote {color: #090;} 106 | .cm-negative {color: #d44;} 107 | .cm-positive {color: #292;} 108 | .cm-header, .cm-strong {font-weight: bold;} 109 | .cm-em {font-style: italic;} 110 | .cm-link {text-decoration: underline;} 111 | .cm-strikethrough {text-decoration: line-through;} 112 | 113 | .cm-s-default .cm-keyword {color: #708;} 114 | .cm-s-default .cm-atom {color: #219;} 115 | .cm-s-default .cm-number {color: #164;} 116 | .cm-s-default .cm-def {color: #00f;} 117 | .cm-s-default .cm-variable, 118 | .cm-s-default .cm-punctuation, 119 | .cm-s-default .cm-property, 120 | .cm-s-default .cm-operator {} 121 | .cm-s-default .cm-variable-2 {color: #05a;} 122 | .cm-s-default .cm-variable-3, .cm-s-default .cm-type {color: #085;} 123 | .cm-s-default .cm-comment {color: #a50;} 124 | .cm-s-default .cm-string {color: #a11;} 125 | .cm-s-default .cm-string-2 {color: #f50;} 126 | .cm-s-default .cm-meta {color: #555;} 127 | .cm-s-default .cm-qualifier {color: #555;} 128 | .cm-s-default .cm-builtin {color: #30a;} 129 | .cm-s-default .cm-bracket {color: #997;} 130 | .cm-s-default .cm-tag {color: #170;} 131 | .cm-s-default .cm-attribute {color: #00c;} 132 | .cm-s-default .cm-hr {color: #999;} 133 | .cm-s-default .cm-link {color: #00c;} 134 | 135 | .cm-s-default .cm-error {color: #f00;} 136 | .cm-invalidchar {color: #f00;} 137 | 138 | .CodeMirror-composing { border-bottom: 2px solid; } 139 | 140 | /* Default styles for common addons */ 141 | 142 | div.CodeMirror span.CodeMirror-matchingbracket {color: #0b0;} 143 | div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #a22;} 144 | .CodeMirror-matchingtag { background: rgba(255, 150, 0, .3); } 145 | .CodeMirror-activeline-background {background: #e8f2ff;} 146 | 147 | /* STOP */ 148 | 149 | /* The rest of this file contains styles related to the mechanics of 150 | the editor. You probably shouldn't touch them. */ 151 | 152 | .CodeMirror { 153 | position: relative; 154 | overflow: hidden; 155 | background: white; 156 | } 157 | 158 | .CodeMirror-scroll { 159 | overflow: scroll !important; /* Things will break if this is overridden */ 160 | /* 50px is the magic margin used to hide the element's real scrollbars */ 161 | /* See overflow: hidden in .CodeMirror */ 162 | margin-bottom: -50px; margin-right: -50px; 163 | padding-bottom: 50px; 164 | height: 100%; 165 | outline: none; /* Prevent dragging from highlighting the element */ 166 | position: relative; 167 | z-index: 0; 168 | } 169 | .CodeMirror-sizer { 170 | position: relative; 171 | border-right: 50px solid transparent; 172 | } 173 | 174 | /* The fake, visible scrollbars. Used to force redraw during scrolling 175 | before actual scrolling happens, thus preventing shaking and 176 | flickering artifacts. */ 177 | .CodeMirror-vscrollbar, .CodeMirror-hscrollbar, .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler { 178 | position: absolute; 179 | z-index: 6; 180 | display: none; 181 | outline: none; 182 | } 183 | .CodeMirror-vscrollbar { 184 | right: 0; top: 0; 185 | overflow-x: hidden; 186 | overflow-y: scroll; 187 | } 188 | .CodeMirror-hscrollbar { 189 | bottom: 0; left: 0; 190 | overflow-y: hidden; 191 | overflow-x: scroll; 192 | } 193 | .CodeMirror-scrollbar-filler { 194 | right: 0; bottom: 0; 195 | } 196 | .CodeMirror-gutter-filler { 197 | left: 0; bottom: 0; 198 | } 199 | 200 | .CodeMirror-gutters { 201 | position: absolute; left: 0; top: 0; 202 | min-height: 100%; 203 | z-index: 3; 204 | } 205 | .CodeMirror-gutter { 206 | white-space: normal; 207 | height: 100%; 208 | display: inline-block; 209 | vertical-align: top; 210 | margin-bottom: -50px; 211 | } 212 | .CodeMirror-gutter-wrapper { 213 | position: absolute; 214 | z-index: 4; 215 | background: none !important; 216 | border: none !important; 217 | } 218 | .CodeMirror-gutter-background { 219 | position: absolute; 220 | top: 0; bottom: 0; 221 | z-index: 4; 222 | } 223 | .CodeMirror-gutter-elt { 224 | position: absolute; 225 | cursor: default; 226 | z-index: 4; 227 | } 228 | .CodeMirror-gutter-wrapper ::selection { background-color: transparent } 229 | .CodeMirror-gutter-wrapper ::-moz-selection { background-color: transparent } 230 | 231 | .CodeMirror-lines { 232 | cursor: text; 233 | min-height: 1px; /* prevents collapsing before first draw */ 234 | } 235 | .CodeMirror pre.CodeMirror-line, 236 | .CodeMirror pre.CodeMirror-line-like { 237 | /* Reset some styles that the rest of the page might have set */ 238 | -moz-border-radius: 0; -webkit-border-radius: 0; border-radius: 0; 239 | border-width: 0; 240 | background: transparent; 241 | font-family: inherit; 242 | font-size: inherit; 243 | margin: 0; 244 | white-space: pre; 245 | word-wrap: normal; 246 | line-height: inherit; 247 | color: inherit; 248 | z-index: 2; 249 | position: relative; 250 | overflow: visible; 251 | -webkit-tap-highlight-color: transparent; 252 | -webkit-font-variant-ligatures: contextual; 253 | font-variant-ligatures: contextual; 254 | } 255 | .CodeMirror-wrap pre.CodeMirror-line, 256 | .CodeMirror-wrap pre.CodeMirror-line-like { 257 | word-wrap: break-word; 258 | white-space: pre-wrap; 259 | word-break: normal; 260 | } 261 | 262 | .CodeMirror-linebackground { 263 | position: absolute; 264 | left: 0; right: 0; top: 0; bottom: 0; 265 | z-index: 0; 266 | } 267 | 268 | .CodeMirror-linewidget { 269 | position: relative; 270 | z-index: 2; 271 | padding: 0.1px; /* Force widget margins to stay inside of the container */ 272 | } 273 | 274 | .CodeMirror-widget {} 275 | 276 | .CodeMirror-rtl pre { direction: rtl; } 277 | 278 | .CodeMirror-code { 279 | outline: none; 280 | } 281 | 282 | /* Force content-box sizing for the elements where we expect it */ 283 | .CodeMirror-scroll, 284 | .CodeMirror-sizer, 285 | .CodeMirror-gutter, 286 | .CodeMirror-gutters, 287 | .CodeMirror-linenumber { 288 | -moz-box-sizing: content-box; 289 | box-sizing: content-box; 290 | } 291 | 292 | .CodeMirror-measure { 293 | position: absolute; 294 | width: 100%; 295 | height: 0; 296 | overflow: hidden; 297 | visibility: hidden; 298 | } 299 | 300 | .CodeMirror-cursor { 301 | position: absolute; 302 | pointer-events: none; 303 | } 304 | .CodeMirror-measure pre { position: static; } 305 | 306 | div.CodeMirror-cursors { 307 | visibility: hidden; 308 | position: relative; 309 | z-index: 3; 310 | } 311 | div.CodeMirror-dragcursors { 312 | visibility: visible; 313 | } 314 | 315 | .CodeMirror-focused div.CodeMirror-cursors { 316 | visibility: visible; 317 | } 318 | 319 | .CodeMirror-selected { background: #d9d9d9; } 320 | .CodeMirror-focused .CodeMirror-selected { background: #d7d4f0; } 321 | .CodeMirror-crosshair { cursor: crosshair; } 322 | .CodeMirror-line::selection, .CodeMirror-line > span::selection, .CodeMirror-line > span > span::selection { background: #d7d4f0; } 323 | .CodeMirror-line::-moz-selection, .CodeMirror-line > span::-moz-selection, .CodeMirror-line > span > span::-moz-selection { background: #d7d4f0; } 324 | 325 | .cm-searching { 326 | background-color: #ffa; 327 | background-color: rgba(255, 255, 0, .4); 328 | } 329 | 330 | /* Used to force a border model for a node */ 331 | .cm-force-border { padding-right: .1px; } 332 | 333 | @media print { 334 | /* Hide the cursor when printing */ 335 | .CodeMirror div.CodeMirror-cursors { 336 | visibility: hidden; 337 | } 338 | } 339 | 340 | /* See issue #2901 */ 341 | .cm-tab-wrap-hack:after { content: ''; } 342 | 343 | /* Help users use markselection to safely style text background */ 344 | span.CodeMirror-selectedtext { background: none; } 345 | -------------------------------------------------------------------------------- /public/css/codemirror/dracula.css: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Name: dracula 4 | Author: Michael Kaminsky (http://github.com/mkaminsky11) 5 | 6 | Original dracula color scheme by Zeno Rocha (https://github.com/zenorocha/dracula-theme) 7 | 8 | */ 9 | 10 | 11 | .cm-s-dracula.CodeMirror, .cm-s-dracula .CodeMirror-gutters { 12 | background-color: #282a36 !important; 13 | color: #f8f8f2 !important; 14 | border: none; 15 | } 16 | .cm-s-dracula .CodeMirror-gutters { color: #282a36; } 17 | .cm-s-dracula .CodeMirror-cursor { border-left: solid thin #f8f8f0; } 18 | .cm-s-dracula .CodeMirror-linenumber { color: #6D8A88; } 19 | .cm-s-dracula .CodeMirror-selected { background: rgba(255, 255, 255, 0.10); } 20 | .cm-s-dracula .CodeMirror-line::selection, .cm-s-dracula .CodeMirror-line > span::selection, .cm-s-dracula .CodeMirror-line > span > span::selection { background: rgba(255, 255, 255, 0.10); } 21 | .cm-s-dracula .CodeMirror-line::-moz-selection, .cm-s-dracula .CodeMirror-line > span::-moz-selection, .cm-s-dracula .CodeMirror-line > span > span::-moz-selection { background: rgba(255, 255, 255, 0.10); } 22 | .cm-s-dracula span.cm-comment { color: #6272a4; } 23 | .cm-s-dracula span.cm-string, .cm-s-dracula span.cm-string-2 { color: #f1fa8c; } 24 | .cm-s-dracula span.cm-number { color: #bd93f9; } 25 | .cm-s-dracula span.cm-variable { color: #50fa7b; } 26 | .cm-s-dracula span.cm-variable-2 { color: white; } 27 | .cm-s-dracula span.cm-def { color: #50fa7b; } 28 | .cm-s-dracula span.cm-operator { color: #ff79c6; } 29 | .cm-s-dracula span.cm-keyword { color: #ff79c6; } 30 | .cm-s-dracula span.cm-atom { color: #bd93f9; } 31 | .cm-s-dracula span.cm-meta { color: #f8f8f2; } 32 | .cm-s-dracula span.cm-tag { color: #ff79c6; } 33 | .cm-s-dracula span.cm-attribute { color: #50fa7b; } 34 | .cm-s-dracula span.cm-qualifier { color: #50fa7b; } 35 | .cm-s-dracula span.cm-property { color: #66d9ef; } 36 | .cm-s-dracula span.cm-builtin { color: #50fa7b; } 37 | .cm-s-dracula span.cm-variable-3, .cm-s-dracula span.cm-type { color: #ffb86c; } 38 | 39 | .cm-s-dracula .CodeMirror-activeline-background { background: rgba(255,255,255,0.1); } 40 | .cm-s-dracula .CodeMirror-matchingbracket { text-decoration: underline; color: white !important; } 41 | -------------------------------------------------------------------------------- /public/css/codemirror/show-hint.css: -------------------------------------------------------------------------------- 1 | .CodeMirror-hints { 2 | position: absolute; 3 | z-index: 10; 4 | overflow: hidden; 5 | list-style: none; 6 | 7 | margin: 0; 8 | padding: 2px; 9 | 10 | -webkit-box-shadow: 2px 3px 5px rgba(0,0,0,.2); 11 | -moz-box-shadow: 2px 3px 5px rgba(0,0,0,.2); 12 | box-shadow: 2px 3px 5px rgba(0,0,0,.2); 13 | border-radius: 3px; 14 | 15 | background: #151a28; 16 | font-size: 16px; 17 | font-family: monospace; 18 | 19 | max-height: 20em; 20 | overflow-y: auto; 21 | } 22 | 23 | .CodeMirror-hint { 24 | color: white; 25 | margin: 0; 26 | padding: 0 4px; 27 | border-radius: 2px; 28 | white-space: pre; 29 | cursor: pointer; 30 | } 31 | 32 | li.CodeMirror-hint-active { 33 | background: #3be8b0; 34 | color: #36395A; 35 | } 36 | -------------------------------------------------------------------------------- /public/css/home.css: -------------------------------------------------------------------------------- 1 | @import url('https://fonts.googleapis.com/css2?family=Open+Sans:ital,wght@0,400;0,500;0,600;0,700;1,400;1,500;1,600;1,700&display=swap'); 2 | 3 | html * { 4 | font-family: 'Open Sans', sans-serif; 5 | color: white; 6 | } 7 | 8 | body{ 9 | height: 90vh; 10 | margin: 0; 11 | background-color: #151a28; 12 | font-size: 10px; 13 | } 14 | 15 | #header{ 16 | height: 10vh; 17 | background-color: #151a28; 18 | position: relative; 19 | } 20 | 21 | #header-title{ 22 | position: absolute; 23 | font-size: 25px; 24 | top: 25%; 25 | left: 1%; 26 | } 27 | 28 | #main-div{ 29 | display: flex; 30 | flex-direction: column; 31 | justify-content: center; 32 | align-items: center; 33 | padding: 0 7em; 34 | } 35 | 36 | #description h1{ 37 | text-align: center; 38 | font-size: 5em; 39 | } 40 | 41 | #description p{ 42 | text-align: center; 43 | font-size: 2.5em; 44 | } 45 | 46 | #description #free-to-use{ 47 | font-size: 1.8em; 48 | } 49 | 50 | #free-to-use a{ 51 | color: aqua; 52 | } 53 | 54 | #start-btn{ 55 | height: 75px; 56 | width: 250px; 57 | font-size: 20px; 58 | 59 | margin: 80px 0; 60 | 61 | align-items: center; 62 | appearance: none; 63 | color: #36395A ; 64 | background-color: #3be8b0; 65 | box-shadow: #44475a 0 2px 4px,#44475a 0 7px 13px -3px, #1FB264 0 -3px 0 inset; 66 | border-radius: 4px; 67 | border-width: 0; 68 | box-sizing: border-box; 69 | cursor: pointer; 70 | display: inline-flex; 71 | justify-content: center; 72 | line-height: 1; 73 | list-style: none; 74 | overflow: hidden; 75 | padding-left: 16px; 76 | padding-right: 16px; 77 | position: relative; 78 | text-align: left; 79 | text-decoration: none; 80 | transition: box-shadow .15s,transform .15s; 81 | user-select: none; 82 | -webkit-user-select: none; 83 | touch-action: manipulation; 84 | white-space: nowrap; 85 | will-change: box-shadow,transform; 86 | 87 | } 88 | 89 | #start-btn:focus { 90 | box-shadow: #D6D6E7 0 0 0 1.5px inset, rgba(45, 35, 66, 0.4) 0 2px 4px, rgba(45, 35, 66, 0.3) 0 7px 13px -3px, #D6D6E7 0 -3px 0 inset; 91 | } 92 | 93 | #start-btn:hover { 94 | box-shadow: #44475a 0 2px 4px,#44475a 0 7px 13px -3px,#1FB264 0 -3px 0 inset; 95 | transform: translateY(-2px); 96 | } 97 | 98 | #start-btn:active { 99 | box-shadow: #D6D6E7 0 3px 7px inset; 100 | transform: translateY(2px); 101 | } 102 | 103 | #glimpse{ 104 | display: flex; 105 | justify-content: center; 106 | margin: 6em 0; 107 | } 108 | 109 | #glimpse-img{ 110 | width: 85%; 111 | } 112 | 113 | #features{ 114 | display: flex; 115 | column-gap: 4em; 116 | } 117 | 118 | .feature{ 119 | flex: 1; 120 | } 121 | 122 | .feature-description{ 123 | font-size: 2em; 124 | } 125 | 126 | footer{ 127 | margin-top: 4em; 128 | padding: 1em; 129 | } 130 | 131 | #footer p{ 132 | font-size: 1.5em; 133 | text-align: center; 134 | } 135 | 136 | @media (max-width: 800px) { 137 | #header-title{ 138 | left: 5%; 139 | } 140 | 141 | body{ 142 | font-size: 7px; 143 | } 144 | 145 | #start-btn{ 146 | margin: 40px 0; 147 | padding-left: 10px; 148 | padding-right: 10px; 149 | width: 200px; 150 | height: 60px; 151 | font-size: 2.4em; 152 | } 153 | 154 | #glimpse-img{ 155 | width: 100%; 156 | } 157 | 158 | #features { 159 | flex-direction: column; 160 | row-gap: 50px; 161 | } 162 | } -------------------------------------------------------------------------------- /public/css/liveBoard.css: -------------------------------------------------------------------------------- 1 | #canvasContainer { 2 | position: relative; 3 | 4 | display: flex; 5 | justify-content: center; 6 | align-items: center; 7 | 8 | width: 100%; 9 | /* height: 70%; */ 10 | 11 | background-color: #282a36; 12 | 13 | cursor: none; 14 | 15 | overflow: hidden; 16 | } 17 | 18 | #drawing-cursor { 19 | position: absolute; 20 | width: 5px; 21 | height: 5px; 22 | border-radius: 50%; 23 | border: 2px solid white; 24 | background-color: #3be8b0; 25 | 26 | z-index: 1; 27 | cursor: none; 28 | } 29 | 30 | #settings-div { 31 | position: absolute; 32 | display: flex; 33 | column-gap: 20px; 34 | justify-content: center; 35 | align-items: center; 36 | 37 | bottom: 10px; 38 | border: none; 39 | border-radius: 5px; 40 | 41 | padding: 5px 10px; 42 | 43 | box-shadow: 0 0 1rem 0 rgba(0, 0, 0, 0.26); 44 | 45 | background-color: #e7eff61e; 46 | backdrop-filter: blur(5px); 47 | 48 | padding: 8px 20px; 49 | 50 | z-index: 10; 51 | 52 | cursor: context-menu; 53 | } 54 | 55 | .setting-icon { 56 | background-size: 80%; 57 | background-position: center; 58 | background-repeat: no-repeat; 59 | 60 | display: grid; 61 | place-items: center; 62 | 63 | cursor: pointer; 64 | 65 | width: 32px; 66 | height: 32px; 67 | 68 | border: none; 69 | border-radius: 5px; 70 | 71 | transition: background-color 0.5s ease-in-out; 72 | } 73 | 74 | .setting-icon:hover { 75 | background-color: #888; 76 | } 77 | 78 | .color-unit { 79 | width: 22px; 80 | height: 22px; 81 | border-radius: 50%; 82 | 83 | cursor: pointer; 84 | } 85 | 86 | #color1 { 87 | background-color: #3be8b0; 88 | } 89 | 90 | #color-picker { 91 | background-image: url("../icons/color-palette.png"); 92 | } 93 | 94 | #brush-size { 95 | background-image: url("../icons/brush.png"); 96 | background-size: 65%; 97 | } 98 | 99 | #eraser { 100 | background-image: url("../icons/eraser.png"); 101 | } 102 | 103 | #clear-canvas { 104 | background-image: url("../icons/clear.png"); 105 | } 106 | 107 | #undoBtn { 108 | background-image: url("../icons/undo.png"); 109 | } 110 | 111 | #redoBtn { 112 | background-image: url("../icons/redo.png"); 113 | } 114 | 115 | .slidecontainer { 116 | position: absolute; 117 | bottom: 50px; 118 | left: 65px; 119 | 120 | border: none; 121 | border-radius: 5px; 122 | 123 | display: none; 124 | padding: 5px 10px; 125 | 126 | box-shadow: 0 0 1rem 0 rgba(0, 0, 0, 0.26); 127 | 128 | background-color: #e7eff61e; 129 | backdrop-filter: blur(5px); 130 | 131 | transition: width 1s ease-in-out; 132 | } 133 | 134 | #stroke-color { 135 | position: absolute; 136 | left: 60px; 137 | z-index: -1; 138 | visibility: hidden; 139 | } 140 | 141 | input[type="color"]::-webkit-color-swatch-wrapper { 142 | padding: 0; 143 | } 144 | 145 | input[type="color"]::-webkit-color-swatch { 146 | border: none; 147 | } -------------------------------------------------------------------------------- /public/css/room.css: -------------------------------------------------------------------------------- 1 | @import url('https://fonts.googleapis.com/css2?family=Open+Sans:ital,wght@0,400;0,500;0,600;0,700;1,400;1,500;1,600;1,700&display=swap'); 2 | 3 | * { 4 | box-sizing: border-box; 5 | } 6 | 7 | html { 8 | font-family: 'Open Sans', sans-serif; 9 | } 10 | 11 | body { 12 | margin: 0; 13 | background-color: #151a28; 14 | } 15 | 16 | main { 17 | height: 100vh; 18 | display: flex; 19 | flex-direction: column; 20 | } 21 | 22 | #header { 23 | height: 8%; 24 | background-color: #151a28; 25 | position: relative; 26 | } 27 | 28 | #header-title { 29 | color: white; 30 | position: absolute; 31 | font-size: 22px; 32 | top: 25%; 33 | left: 1%; 34 | } 35 | 36 | #main-div { 37 | display: flex; 38 | height: 84%; 39 | gap: 15px; 40 | padding: 0 15px; 41 | } 42 | 43 | .left-div { 44 | display: flex; 45 | flex-direction: column; 46 | width: 50%; 47 | gap: 15px; 48 | height: 100%; 49 | } 50 | 51 | .right-div { 52 | display: flex; 53 | flex-direction: column; 54 | width: 50%; 55 | gap: 15px; 56 | height: 100%; 57 | } 58 | 59 | #code-area { 60 | width: 100%; 61 | height: 70%; 62 | position: relative; 63 | } 64 | 65 | #language { 66 | position: absolute; 67 | top: 1%; 68 | right: 2%; 69 | z-index: 2; 70 | } 71 | 72 | #language-dropdown { 73 | padding: 5px; 74 | height: 100%; 75 | left: 45%; 76 | background: #151a28; 77 | color: #3be8b0; 78 | border-radius: 5px; 79 | font-size: 15px; 80 | } 81 | 82 | #firepad { 83 | height: 100%; 84 | width: 100%; 85 | } 86 | 87 | .CodeMirror { 88 | font-size: 16px; 89 | } 90 | 91 | 92 | #run-code-btn { 93 | width: fit-content; 94 | position: absolute; 95 | top: 15%; 96 | left: 50%; 97 | 98 | align-items: center; 99 | appearance: none; 100 | color: #36395A; 101 | background-color: #3be8b0; 102 | box-shadow: #44475a 0 2px 4px, #44475a 0 7px 13px -3px, #1FB264 0 -3px 0 inset; 103 | border-radius: 4px; 104 | border-width: 0; 105 | box-sizing: border-box; 106 | cursor: pointer; 107 | display: inline-flex; 108 | height: 70%; 109 | justify-content: center; 110 | line-height: 1; 111 | list-style: none; 112 | overflow: hidden; 113 | padding-left: 16px; 114 | padding-right: 16px; 115 | position: relative; 116 | text-align: left; 117 | text-decoration: none; 118 | transition: box-shadow .15s, transform .15s; 119 | user-select: none; 120 | -webkit-user-select: none; 121 | touch-action: manipulation; 122 | white-space: nowrap; 123 | will-change: box-shadow, transform; 124 | 125 | } 126 | 127 | #run-code-btn:focus { 128 | box-shadow: #D6D6E7 0 0 0 1.5px inset, rgba(45, 35, 66, 0.4) 0 2px 4px, rgba(45, 35, 66, 0.3) 0 7px 13px -3px, #D6D6E7 0 -3px 0 inset; 129 | } 130 | 131 | #run-code-btn:hover { 132 | box-shadow: #44475a 0 2px 4px, #44475a 0 7px 13px -3px, #1FB264 0 -3px 0 inset; 133 | transform: translateY(-2px); 134 | } 135 | 136 | #run-code-btn:active { 137 | box-shadow: #D6D6E7 0 3px 7px inset; 138 | transform: translateY(2px); 139 | } 140 | 141 | #share-btn { 142 | position: absolute; 143 | top: 25%; 144 | right: 2%; 145 | color: #3be8b0; 146 | } 147 | 148 | .swal-modal { 149 | background-color: #282a36; 150 | } 151 | 152 | .swal-title { 153 | color: white; 154 | } 155 | 156 | .swal-text { 157 | color: white; 158 | font-size: 18px; 159 | } 160 | 161 | .swal-button { 162 | color: #36395A; 163 | background-color: #3be8b0; 164 | } 165 | 166 | .swal-content input { 167 | width: 100%; 168 | height: 28px; 169 | font-size: 16px; 170 | background-color: #3b3e50; 171 | color: white; 172 | border: none; 173 | } 174 | 175 | #input-output { 176 | display: flex; 177 | height: 30%; 178 | gap: 15px; 179 | } 180 | 181 | #input { 182 | width: 50%; 183 | height: 100%; 184 | resize: none; 185 | color: white; 186 | background-color: #282a36; 187 | padding: 10px; 188 | border: none; 189 | } 190 | 191 | #input:focus { 192 | outline: none; 193 | font-size: 16px; 194 | font-family: 'Open Sans', sans-serif; 195 | color: white; 196 | } 197 | 198 | #input::placeholder { 199 | font-size: 16px; 200 | font-family: 'Open Sans', sans-serif; 201 | color: white; 202 | } 203 | 204 | #output { 205 | width: 50%; 206 | height: 100%; 207 | position: relative; 208 | background-color: #282a36; 209 | color: white; 210 | padding: 10px; 211 | } 212 | 213 | #output-text { 214 | max-height: 210px; 215 | overflow: auto; 216 | } 217 | 218 | .spin { 219 | position: absolute; 220 | top: 36%; 221 | left: 44%; 222 | display: none; 223 | width: 50px; 224 | height: 50px; 225 | border: 3px solid rgba(255, 255, 255, .3); 226 | border-radius: 50%; 227 | border-top-color: #fff; 228 | animation: spin 1s ease-in-out infinite; 229 | -webkit-animation: spin 1s ease-in-out infinite; 230 | } 231 | 232 | @keyframes spin { 233 | to { 234 | -webkit-transform: rotate(360deg); 235 | } 236 | } 237 | 238 | @-webkit-keyframes spin { 239 | to { 240 | -webkit-transform: rotate(360deg); 241 | } 242 | } 243 | 244 | #video-grid { 245 | display: flex; 246 | justify-content: flex-start; 247 | flex-wrap: wrap; 248 | gap: 15px; 249 | } 250 | 251 | video { 252 | object-fit: cover; 253 | } 254 | 255 | .video-player { 256 | height: 20vh; 257 | width: 20vh; 258 | } 259 | 260 | #controls { 261 | height: 8%; 262 | display: flex; 263 | justify-content: center; 264 | align-items: center; 265 | background-color: #151a28; 266 | } 267 | 268 | .material-icons { 269 | font-size: 32px; 270 | cursor: pointer; 271 | } 272 | 273 | .control-btn { 274 | background-color: #3be8b0; 275 | width: fit-content; 276 | padding: 8px; 277 | margin: 10px; 278 | border-radius: 3rem; 279 | border: none; 280 | } 281 | 282 | .powered-by-firepad { 283 | display: none; 284 | } 285 | 286 | /* width */ 287 | ::-webkit-scrollbar { 288 | width: 0.75rem; 289 | } 290 | 291 | /* Track */ 292 | ::-webkit-scrollbar-track { 293 | background: #282a36; 294 | border-radius: 0.5rem; 295 | } 296 | 297 | /* Handle */ 298 | ::-webkit-scrollbar-thumb { 299 | background: #888; 300 | border-radius: 0.5rem; 301 | } 302 | 303 | /* Handle on hover */ 304 | ::-webkit-scrollbar-thumb:hover { 305 | background: #555; 306 | } -------------------------------------------------------------------------------- /public/glimpse.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Swanand01/CollabCode/d110d05edfa5c76b14da247a301c39d14f30bc2d/public/glimpse.png -------------------------------------------------------------------------------- /public/icons/brush.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Swanand01/CollabCode/d110d05edfa5c76b14da247a301c39d14f30bc2d/public/icons/brush.png -------------------------------------------------------------------------------- /public/icons/clear.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Swanand01/CollabCode/d110d05edfa5c76b14da247a301c39d14f30bc2d/public/icons/clear.png -------------------------------------------------------------------------------- /public/icons/color-palette.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Swanand01/CollabCode/d110d05edfa5c76b14da247a301c39d14f30bc2d/public/icons/color-palette.png -------------------------------------------------------------------------------- /public/icons/eraser.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Swanand01/CollabCode/d110d05edfa5c76b14da247a301c39d14f30bc2d/public/icons/eraser.png -------------------------------------------------------------------------------- /public/icons/redo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Swanand01/CollabCode/d110d05edfa5c76b14da247a301c39d14f30bc2d/public/icons/redo.png -------------------------------------------------------------------------------- /public/icons/undo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Swanand01/CollabCode/d110d05edfa5c76b14da247a301c39d14f30bc2d/public/icons/undo.png -------------------------------------------------------------------------------- /public/js/codemirror/clike.js: -------------------------------------------------------------------------------- 1 | // CodeMirror, copyright (c) by Marijn Haverbeke and others 2 | // Distributed under an MIT license: https://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 | 14 | function Context(indented, column, type, info, align, prev) { 15 | this.indented = indented; 16 | this.column = column; 17 | this.type = type; 18 | this.info = info; 19 | this.align = align; 20 | this.prev = prev; 21 | } 22 | function pushContext(state, col, type, info) { 23 | var indent = state.indented; 24 | if (state.context && state.context.type == "statement" && type != "statement") 25 | indent = state.context.indented; 26 | return state.context = new Context(indent, col, type, info, null, state.context); 27 | } 28 | function popContext(state) { 29 | var t = state.context.type; 30 | if (t == ")" || t == "]" || t == "}") 31 | state.indented = state.context.indented; 32 | return state.context = state.context.prev; 33 | } 34 | 35 | function typeBefore(stream, state, pos) { 36 | if (state.prevToken == "variable" || state.prevToken == "type") return true; 37 | if (/\S(?:[^- ]>|[*\]])\s*$|\*$/.test(stream.string.slice(0, pos))) return true; 38 | if (state.typeAtEndOfLine && stream.column() == stream.indentation()) return true; 39 | } 40 | 41 | function isTopScope(context) { 42 | for (;;) { 43 | if (!context || context.type == "top") return true; 44 | if (context.type == "}" && context.prev.info != "namespace") return false; 45 | context = context.prev; 46 | } 47 | } 48 | 49 | CodeMirror.defineMode("clike", function(config, parserConfig) { 50 | var indentUnit = config.indentUnit, 51 | statementIndentUnit = parserConfig.statementIndentUnit || indentUnit, 52 | dontAlignCalls = parserConfig.dontAlignCalls, 53 | keywords = parserConfig.keywords || {}, 54 | types = parserConfig.types || {}, 55 | builtin = parserConfig.builtin || {}, 56 | blockKeywords = parserConfig.blockKeywords || {}, 57 | defKeywords = parserConfig.defKeywords || {}, 58 | atoms = parserConfig.atoms || {}, 59 | hooks = parserConfig.hooks || {}, 60 | multiLineStrings = parserConfig.multiLineStrings, 61 | indentStatements = parserConfig.indentStatements !== false, 62 | indentSwitch = parserConfig.indentSwitch !== false, 63 | namespaceSeparator = parserConfig.namespaceSeparator, 64 | isPunctuationChar = parserConfig.isPunctuationChar || /[\[\]{}\(\),;\:\.]/, 65 | numberStart = parserConfig.numberStart || /[\d\.]/, 66 | number = parserConfig.number || /^(?:0x[a-f\d]+|0b[01]+|(?:\d+\.?\d*|\.\d+)(?:e[-+]?\d+)?)(u|ll?|l|f)?/i, 67 | isOperatorChar = parserConfig.isOperatorChar || /[+\-*&%=<>!?|\/]/, 68 | isIdentifierChar = parserConfig.isIdentifierChar || /[\w\$_\xa1-\uffff]/, 69 | // An optional function that takes a {string} token and returns true if it 70 | // should be treated as a builtin. 71 | isReservedIdentifier = parserConfig.isReservedIdentifier || false; 72 | 73 | var curPunc, isDefKeyword; 74 | 75 | function tokenBase(stream, state) { 76 | var ch = stream.next(); 77 | if (hooks[ch]) { 78 | var result = hooks[ch](stream, state); 79 | if (result !== false) return result; 80 | } 81 | if (ch == '"' || ch == "'") { 82 | state.tokenize = tokenString(ch); 83 | return state.tokenize(stream, state); 84 | } 85 | if (numberStart.test(ch)) { 86 | stream.backUp(1) 87 | if (stream.match(number)) return "number" 88 | stream.next() 89 | } 90 | if (isPunctuationChar.test(ch)) { 91 | curPunc = ch; 92 | return null; 93 | } 94 | if (ch == "/") { 95 | if (stream.eat("*")) { 96 | state.tokenize = tokenComment; 97 | return tokenComment(stream, state); 98 | } 99 | if (stream.eat("/")) { 100 | stream.skipToEnd(); 101 | return "comment"; 102 | } 103 | } 104 | if (isOperatorChar.test(ch)) { 105 | while (!stream.match(/^\/[\/*]/, false) && stream.eat(isOperatorChar)) {} 106 | return "operator"; 107 | } 108 | stream.eatWhile(isIdentifierChar); 109 | if (namespaceSeparator) while (stream.match(namespaceSeparator)) 110 | stream.eatWhile(isIdentifierChar); 111 | 112 | var cur = stream.current(); 113 | if (contains(keywords, cur)) { 114 | if (contains(blockKeywords, cur)) curPunc = "newstatement"; 115 | if (contains(defKeywords, cur)) isDefKeyword = true; 116 | return "keyword"; 117 | } 118 | if (contains(types, cur)) return "type"; 119 | if (contains(builtin, cur) 120 | || (isReservedIdentifier && isReservedIdentifier(cur))) { 121 | if (contains(blockKeywords, cur)) curPunc = "newstatement"; 122 | return "builtin"; 123 | } 124 | if (contains(atoms, cur)) return "atom"; 125 | return "variable"; 126 | } 127 | 128 | function tokenString(quote) { 129 | return function(stream, state) { 130 | var escaped = false, next, end = false; 131 | while ((next = stream.next()) != null) { 132 | if (next == quote && !escaped) {end = true; break;} 133 | escaped = !escaped && next == "\\"; 134 | } 135 | if (end || !(escaped || multiLineStrings)) 136 | state.tokenize = null; 137 | return "string"; 138 | }; 139 | } 140 | 141 | function tokenComment(stream, state) { 142 | var maybeEnd = false, ch; 143 | while (ch = stream.next()) { 144 | if (ch == "/" && maybeEnd) { 145 | state.tokenize = null; 146 | break; 147 | } 148 | maybeEnd = (ch == "*"); 149 | } 150 | return "comment"; 151 | } 152 | 153 | function maybeEOL(stream, state) { 154 | if (parserConfig.typeFirstDefinitions && stream.eol() && isTopScope(state.context)) 155 | state.typeAtEndOfLine = typeBefore(stream, state, stream.pos) 156 | } 157 | 158 | // Interface 159 | 160 | return { 161 | startState: function(basecolumn) { 162 | return { 163 | tokenize: null, 164 | context: new Context((basecolumn || 0) - indentUnit, 0, "top", null, false), 165 | indented: 0, 166 | startOfLine: true, 167 | prevToken: null 168 | }; 169 | }, 170 | 171 | token: function(stream, state) { 172 | var ctx = state.context; 173 | if (stream.sol()) { 174 | if (ctx.align == null) ctx.align = false; 175 | state.indented = stream.indentation(); 176 | state.startOfLine = true; 177 | } 178 | if (stream.eatSpace()) { maybeEOL(stream, state); return null; } 179 | curPunc = isDefKeyword = null; 180 | var style = (state.tokenize || tokenBase)(stream, state); 181 | if (style == "comment" || style == "meta") return style; 182 | if (ctx.align == null) ctx.align = true; 183 | 184 | if (curPunc == ";" || curPunc == ":" || (curPunc == "," && stream.match(/^\s*(?:\/\/.*)?$/, false))) 185 | while (state.context.type == "statement") popContext(state); 186 | else if (curPunc == "{") pushContext(state, stream.column(), "}"); 187 | else if (curPunc == "[") pushContext(state, stream.column(), "]"); 188 | else if (curPunc == "(") pushContext(state, stream.column(), ")"); 189 | else if (curPunc == "}") { 190 | while (ctx.type == "statement") ctx = popContext(state); 191 | if (ctx.type == "}") ctx = popContext(state); 192 | while (ctx.type == "statement") ctx = popContext(state); 193 | } 194 | else if (curPunc == ctx.type) popContext(state); 195 | else if (indentStatements && 196 | (((ctx.type == "}" || ctx.type == "top") && curPunc != ";") || 197 | (ctx.type == "statement" && curPunc == "newstatement"))) { 198 | pushContext(state, stream.column(), "statement", stream.current()); 199 | } 200 | 201 | if (style == "variable" && 202 | ((state.prevToken == "def" || 203 | (parserConfig.typeFirstDefinitions && typeBefore(stream, state, stream.start) && 204 | isTopScope(state.context) && stream.match(/^\s*\(/, false))))) 205 | style = "def"; 206 | 207 | if (hooks.token) { 208 | var result = hooks.token(stream, state, style); 209 | if (result !== undefined) style = result; 210 | } 211 | 212 | if (style == "def" && parserConfig.styleDefs === false) style = "variable"; 213 | 214 | state.startOfLine = false; 215 | state.prevToken = isDefKeyword ? "def" : style || curPunc; 216 | maybeEOL(stream, state); 217 | return style; 218 | }, 219 | 220 | indent: function(state, textAfter) { 221 | if (state.tokenize != tokenBase && state.tokenize != null || state.typeAtEndOfLine) return CodeMirror.Pass; 222 | var ctx = state.context, firstChar = textAfter && textAfter.charAt(0); 223 | var closing = firstChar == ctx.type; 224 | if (ctx.type == "statement" && firstChar == "}") ctx = ctx.prev; 225 | if (parserConfig.dontIndentStatements) 226 | while (ctx.type == "statement" && parserConfig.dontIndentStatements.test(ctx.info)) 227 | ctx = ctx.prev 228 | if (hooks.indent) { 229 | var hook = hooks.indent(state, ctx, textAfter, indentUnit); 230 | if (typeof hook == "number") return hook 231 | } 232 | var switchBlock = ctx.prev && ctx.prev.info == "switch"; 233 | if (parserConfig.allmanIndentation && /[{(]/.test(firstChar)) { 234 | while (ctx.type != "top" && ctx.type != "}") ctx = ctx.prev 235 | return ctx.indented 236 | } 237 | if (ctx.type == "statement") 238 | return ctx.indented + (firstChar == "{" ? 0 : statementIndentUnit); 239 | if (ctx.align && (!dontAlignCalls || ctx.type != ")")) 240 | return ctx.column + (closing ? 0 : 1); 241 | if (ctx.type == ")" && !closing) 242 | return ctx.indented + statementIndentUnit; 243 | 244 | return ctx.indented + (closing ? 0 : indentUnit) + 245 | (!closing && switchBlock && !/^(?:case|default)\b/.test(textAfter) ? indentUnit : 0); 246 | }, 247 | 248 | electricInput: indentSwitch ? /^\s*(?:case .*?:|default:|\{\}?|\})$/ : /^\s*[{}]$/, 249 | blockCommentStart: "/*", 250 | blockCommentEnd: "*/", 251 | blockCommentContinue: " * ", 252 | lineComment: "//", 253 | fold: "brace" 254 | }; 255 | }); 256 | 257 | function words(str) { 258 | var obj = {}, words = str.split(" "); 259 | for (var i = 0; i < words.length; ++i) obj[words[i]] = true; 260 | return obj; 261 | } 262 | function contains(words, word) { 263 | if (typeof words === "function") { 264 | return words(word); 265 | } else { 266 | return words.propertyIsEnumerable(word); 267 | } 268 | } 269 | var cKeywords = "auto if break case register continue return default do sizeof " + 270 | "static else struct switch extern typedef union for goto while enum const " + 271 | "volatile inline restrict asm fortran"; 272 | 273 | // Keywords from https://en.cppreference.com/w/cpp/keyword includes C++20. 274 | var cppKeywords = "alignas alignof and and_eq audit axiom bitand bitor catch " + 275 | "class compl concept constexpr const_cast decltype delete dynamic_cast " + 276 | "explicit export final friend import module mutable namespace new noexcept " + 277 | "not not_eq operator or or_eq override private protected public " + 278 | "reinterpret_cast requires static_assert static_cast template this " + 279 | "thread_local throw try typeid typename using virtual xor xor_eq"; 280 | 281 | var objCKeywords = "bycopy byref in inout oneway out self super atomic nonatomic retain copy " + 282 | "readwrite readonly strong weak assign typeof nullable nonnull null_resettable _cmd " + 283 | "@interface @implementation @end @protocol @encode @property @synthesize @dynamic @class " + 284 | "@public @package @private @protected @required @optional @try @catch @finally @import " + 285 | "@selector @encode @defs @synchronized @autoreleasepool @compatibility_alias @available"; 286 | 287 | var objCBuiltins = "FOUNDATION_EXPORT FOUNDATION_EXTERN NS_INLINE NS_FORMAT_FUNCTION " + 288 | " NS_RETURNS_RETAINEDNS_ERROR_ENUM NS_RETURNS_NOT_RETAINED NS_RETURNS_INNER_POINTER " + 289 | "NS_DESIGNATED_INITIALIZER NS_ENUM NS_OPTIONS NS_REQUIRES_NIL_TERMINATION " + 290 | "NS_ASSUME_NONNULL_BEGIN NS_ASSUME_NONNULL_END NS_SWIFT_NAME NS_REFINED_FOR_SWIFT" 291 | 292 | // Do not use this. Use the cTypes function below. This is global just to avoid 293 | // excessive calls when cTypes is being called multiple times during a parse. 294 | var basicCTypes = words("int long char short double float unsigned signed " + 295 | "void bool"); 296 | 297 | // Do not use this. Use the objCTypes function below. This is global just to avoid 298 | // excessive calls when objCTypes is being called multiple times during a parse. 299 | var basicObjCTypes = words("SEL instancetype id Class Protocol BOOL"); 300 | 301 | // Returns true if identifier is a "C" type. 302 | // C type is defined as those that are reserved by the compiler (basicTypes), 303 | // and those that end in _t (Reserved by POSIX for types) 304 | // http://www.gnu.org/software/libc/manual/html_node/Reserved-Names.html 305 | function cTypes(identifier) { 306 | return contains(basicCTypes, identifier) || /.+_t$/.test(identifier); 307 | } 308 | 309 | // Returns true if identifier is a "Objective C" type. 310 | function objCTypes(identifier) { 311 | return cTypes(identifier) || contains(basicObjCTypes, identifier); 312 | } 313 | 314 | var cBlockKeywords = "case do else for if switch while struct enum union"; 315 | var cDefKeywords = "struct enum union"; 316 | 317 | function cppHook(stream, state) { 318 | if (!state.startOfLine) return false 319 | for (var ch, next = null; ch = stream.peek();) { 320 | if (ch == "\\" && stream.match(/^.$/)) { 321 | next = cppHook 322 | break 323 | } else if (ch == "/" && stream.match(/^\/[\/\*]/, false)) { 324 | break 325 | } 326 | stream.next() 327 | } 328 | state.tokenize = next 329 | return "meta" 330 | } 331 | 332 | function pointerHook(_stream, state) { 333 | if (state.prevToken == "type") return "type"; 334 | return false; 335 | } 336 | 337 | // For C and C++ (and ObjC): identifiers starting with __ 338 | // or _ followed by a capital letter are reserved for the compiler. 339 | function cIsReservedIdentifier(token) { 340 | if (!token || token.length < 2) return false; 341 | if (token[0] != '_') return false; 342 | return (token[1] == '_') || (token[1] !== token[1].toLowerCase()); 343 | } 344 | 345 | function cpp14Literal(stream) { 346 | stream.eatWhile(/[\w\.']/); 347 | return "number"; 348 | } 349 | 350 | function cpp11StringHook(stream, state) { 351 | stream.backUp(1); 352 | // Raw strings. 353 | if (stream.match(/^(?:R|u8R|uR|UR|LR)/)) { 354 | var match = stream.match(/^"([^\s\\()]{0,16})\(/); 355 | if (!match) { 356 | return false; 357 | } 358 | state.cpp11RawStringDelim = match[1]; 359 | state.tokenize = tokenRawString; 360 | return tokenRawString(stream, state); 361 | } 362 | // Unicode strings/chars. 363 | if (stream.match(/^(?:u8|u|U|L)/)) { 364 | if (stream.match(/^["']/, /* eat */ false)) { 365 | return "string"; 366 | } 367 | return false; 368 | } 369 | // Ignore this hook. 370 | stream.next(); 371 | return false; 372 | } 373 | 374 | function cppLooksLikeConstructor(word) { 375 | var lastTwo = /(\w+)::~?(\w+)$/.exec(word); 376 | return lastTwo && lastTwo[1] == lastTwo[2]; 377 | } 378 | 379 | // C#-style strings where "" escapes a quote. 380 | function tokenAtString(stream, state) { 381 | var next; 382 | while ((next = stream.next()) != null) { 383 | if (next == '"' && !stream.eat('"')) { 384 | state.tokenize = null; 385 | break; 386 | } 387 | } 388 | return "string"; 389 | } 390 | 391 | // C++11 raw string literal is "( anything )", where 392 | // can be a string up to 16 characters long. 393 | function tokenRawString(stream, state) { 394 | // Escape characters that have special regex meanings. 395 | var delim = state.cpp11RawStringDelim.replace(/[^\w\s]/g, '\\$&'); 396 | var match = stream.match(new RegExp(".*?\\)" + delim + '"')); 397 | if (match) 398 | state.tokenize = null; 399 | else 400 | stream.skipToEnd(); 401 | return "string"; 402 | } 403 | 404 | function def(mimes, mode) { 405 | if (typeof mimes == "string") mimes = [mimes]; 406 | var words = []; 407 | function add(obj) { 408 | if (obj) for (var prop in obj) if (obj.hasOwnProperty(prop)) 409 | words.push(prop); 410 | } 411 | add(mode.keywords); 412 | add(mode.types); 413 | add(mode.builtin); 414 | add(mode.atoms); 415 | if (words.length) { 416 | mode.helperType = mimes[0]; 417 | CodeMirror.registerHelper("hintWords", mimes[0], words); 418 | } 419 | 420 | for (var i = 0; i < mimes.length; ++i) 421 | CodeMirror.defineMIME(mimes[i], mode); 422 | } 423 | 424 | def(["text/x-csrc", "text/x-c", "text/x-chdr"], { 425 | name: "clike", 426 | keywords: words(cKeywords), 427 | types: cTypes, 428 | blockKeywords: words(cBlockKeywords), 429 | defKeywords: words(cDefKeywords), 430 | typeFirstDefinitions: true, 431 | atoms: words("NULL true false"), 432 | isReservedIdentifier: cIsReservedIdentifier, 433 | hooks: { 434 | "#": cppHook, 435 | "*": pointerHook, 436 | }, 437 | modeProps: {fold: ["brace", "include"]} 438 | }); 439 | 440 | def(["text/x-c++src", "text/x-c++hdr"], { 441 | name: "clike", 442 | keywords: words(cKeywords + " " + cppKeywords), 443 | types: cTypes, 444 | blockKeywords: words(cBlockKeywords + " class try catch"), 445 | defKeywords: words(cDefKeywords + " class namespace"), 446 | typeFirstDefinitions: true, 447 | atoms: words("true false NULL nullptr"), 448 | dontIndentStatements: /^template$/, 449 | isIdentifierChar: /[\w\$_~\xa1-\uffff]/, 450 | isReservedIdentifier: cIsReservedIdentifier, 451 | hooks: { 452 | "#": cppHook, 453 | "*": pointerHook, 454 | "u": cpp11StringHook, 455 | "U": cpp11StringHook, 456 | "L": cpp11StringHook, 457 | "R": cpp11StringHook, 458 | "0": cpp14Literal, 459 | "1": cpp14Literal, 460 | "2": cpp14Literal, 461 | "3": cpp14Literal, 462 | "4": cpp14Literal, 463 | "5": cpp14Literal, 464 | "6": cpp14Literal, 465 | "7": cpp14Literal, 466 | "8": cpp14Literal, 467 | "9": cpp14Literal, 468 | token: function(stream, state, style) { 469 | if (style == "variable" && stream.peek() == "(" && 470 | (state.prevToken == ";" || state.prevToken == null || 471 | state.prevToken == "}") && 472 | cppLooksLikeConstructor(stream.current())) 473 | return "def"; 474 | } 475 | }, 476 | namespaceSeparator: "::", 477 | modeProps: {fold: ["brace", "include"]} 478 | }); 479 | 480 | def("text/x-java", { 481 | name: "clike", 482 | keywords: words("abstract assert break case catch class const continue default " + 483 | "do else enum extends final finally for goto if implements import " + 484 | "instanceof interface native new package private protected public " + 485 | "return static strictfp super switch synchronized this throw throws transient " + 486 | "try volatile while @interface"), 487 | types: words("var byte short int long float double boolean char void Boolean Byte Character Double Float " + 488 | "Integer Long Number Object Short String StringBuffer StringBuilder Void"), 489 | blockKeywords: words("catch class do else finally for if switch try while"), 490 | defKeywords: words("class interface enum @interface"), 491 | typeFirstDefinitions: true, 492 | atoms: words("true false null"), 493 | number: /^(?:0x[a-f\d_]+|0b[01_]+|(?:[\d_]+\.?\d*|\.\d+)(?:e[-+]?[\d_]+)?)(u|ll?|l|f)?/i, 494 | hooks: { 495 | "@": function(stream) { 496 | // Don't match the @interface keyword. 497 | if (stream.match('interface', false)) return false; 498 | 499 | stream.eatWhile(/[\w\$_]/); 500 | return "meta"; 501 | } 502 | }, 503 | modeProps: {fold: ["brace", "import"]} 504 | }); 505 | 506 | def("text/x-csharp", { 507 | name: "clike", 508 | keywords: words("abstract as async await base break case catch checked class const continue" + 509 | " default delegate do else enum event explicit extern finally fixed for" + 510 | " foreach goto if implicit in interface internal is lock namespace new" + 511 | " operator out override params private protected public readonly ref return sealed" + 512 | " sizeof stackalloc static struct switch this throw try typeof unchecked" + 513 | " unsafe using virtual void volatile while add alias ascending descending dynamic from get" + 514 | " global group into join let orderby partial remove select set value var yield"), 515 | types: words("Action Boolean Byte Char DateTime DateTimeOffset Decimal Double Func" + 516 | " Guid Int16 Int32 Int64 Object SByte Single String Task TimeSpan UInt16 UInt32" + 517 | " UInt64 bool byte char decimal double short int long object" + 518 | " sbyte float string ushort uint ulong"), 519 | blockKeywords: words("catch class do else finally for foreach if struct switch try while"), 520 | defKeywords: words("class interface namespace struct var"), 521 | typeFirstDefinitions: true, 522 | atoms: words("true false null"), 523 | hooks: { 524 | "@": function(stream, state) { 525 | if (stream.eat('"')) { 526 | state.tokenize = tokenAtString; 527 | return tokenAtString(stream, state); 528 | } 529 | stream.eatWhile(/[\w\$_]/); 530 | return "meta"; 531 | } 532 | } 533 | }); 534 | 535 | function tokenTripleString(stream, state) { 536 | var escaped = false; 537 | while (!stream.eol()) { 538 | if (!escaped && stream.match('"""')) { 539 | state.tokenize = null; 540 | break; 541 | } 542 | escaped = stream.next() == "\\" && !escaped; 543 | } 544 | return "string"; 545 | } 546 | 547 | function tokenNestedComment(depth) { 548 | return function (stream, state) { 549 | var ch 550 | while (ch = stream.next()) { 551 | if (ch == "*" && stream.eat("/")) { 552 | if (depth == 1) { 553 | state.tokenize = null 554 | break 555 | } else { 556 | state.tokenize = tokenNestedComment(depth - 1) 557 | return state.tokenize(stream, state) 558 | } 559 | } else if (ch == "/" && stream.eat("*")) { 560 | state.tokenize = tokenNestedComment(depth + 1) 561 | return state.tokenize(stream, state) 562 | } 563 | } 564 | return "comment" 565 | } 566 | } 567 | 568 | def("text/x-scala", { 569 | name: "clike", 570 | keywords: words( 571 | /* scala */ 572 | "abstract case catch class def do else extends final finally for forSome if " + 573 | "implicit import lazy match new null object override package private protected return " + 574 | "sealed super this throw trait try type val var while with yield _ " + 575 | 576 | /* package scala */ 577 | "assert assume require print println printf readLine readBoolean readByte readShort " + 578 | "readChar readInt readLong readFloat readDouble" 579 | ), 580 | types: words( 581 | "AnyVal App Application Array BufferedIterator BigDecimal BigInt Char Console Either " + 582 | "Enumeration Equiv Error Exception Fractional Function IndexedSeq Int Integral Iterable " + 583 | "Iterator List Map Numeric Nil NotNull Option Ordered Ordering PartialFunction PartialOrdering " + 584 | "Product Proxy Range Responder Seq Serializable Set Specializable Stream StringBuilder " + 585 | "StringContext Symbol Throwable Traversable TraversableOnce Tuple Unit Vector " + 586 | 587 | /* package java.lang */ 588 | "Boolean Byte Character CharSequence Class ClassLoader Cloneable Comparable " + 589 | "Compiler Double Exception Float Integer Long Math Number Object Package Pair Process " + 590 | "Runtime Runnable SecurityManager Short StackTraceElement StrictMath String " + 591 | "StringBuffer System Thread ThreadGroup ThreadLocal Throwable Triple Void" 592 | ), 593 | multiLineStrings: true, 594 | blockKeywords: words("catch class enum do else finally for forSome if match switch try while"), 595 | defKeywords: words("class enum def object package trait type val var"), 596 | atoms: words("true false null"), 597 | indentStatements: false, 598 | indentSwitch: false, 599 | isOperatorChar: /[+\-*&%=<>!?|\/#:@]/, 600 | hooks: { 601 | "@": function(stream) { 602 | stream.eatWhile(/[\w\$_]/); 603 | return "meta"; 604 | }, 605 | '"': function(stream, state) { 606 | if (!stream.match('""')) return false; 607 | state.tokenize = tokenTripleString; 608 | return state.tokenize(stream, state); 609 | }, 610 | "'": function(stream) { 611 | stream.eatWhile(/[\w\$_\xa1-\uffff]/); 612 | return "atom"; 613 | }, 614 | "=": function(stream, state) { 615 | var cx = state.context 616 | if (cx.type == "}" && cx.align && stream.eat(">")) { 617 | state.context = new Context(cx.indented, cx.column, cx.type, cx.info, null, cx.prev) 618 | return "operator" 619 | } else { 620 | return false 621 | } 622 | }, 623 | 624 | "/": function(stream, state) { 625 | if (!stream.eat("*")) return false 626 | state.tokenize = tokenNestedComment(1) 627 | return state.tokenize(stream, state) 628 | } 629 | }, 630 | modeProps: {closeBrackets: {pairs: '()[]{}""', triples: '"'}} 631 | }); 632 | 633 | function tokenKotlinString(tripleString){ 634 | return function (stream, state) { 635 | var escaped = false, next, end = false; 636 | while (!stream.eol()) { 637 | if (!tripleString && !escaped && stream.match('"') ) {end = true; break;} 638 | if (tripleString && stream.match('"""')) {end = true; break;} 639 | next = stream.next(); 640 | if(!escaped && next == "$" && stream.match('{')) 641 | stream.skipTo("}"); 642 | escaped = !escaped && next == "\\" && !tripleString; 643 | } 644 | if (end || !tripleString) 645 | state.tokenize = null; 646 | return "string"; 647 | } 648 | } 649 | 650 | def("text/x-kotlin", { 651 | name: "clike", 652 | keywords: words( 653 | /*keywords*/ 654 | "package as typealias class interface this super val operator " + 655 | "var fun for is in This throw return annotation " + 656 | "break continue object if else while do try when !in !is as? " + 657 | 658 | /*soft keywords*/ 659 | "file import where by get set abstract enum open inner override private public internal " + 660 | "protected catch finally out final vararg reified dynamic companion constructor init " + 661 | "sealed field property receiver param sparam lateinit data inline noinline tailrec " + 662 | "external annotation crossinline const operator infix suspend actual expect setparam value" 663 | ), 664 | types: words( 665 | /* package java.lang */ 666 | "Boolean Byte Character CharSequence Class ClassLoader Cloneable Comparable " + 667 | "Compiler Double Exception Float Integer Long Math Number Object Package Pair Process " + 668 | "Runtime Runnable SecurityManager Short StackTraceElement StrictMath String " + 669 | "StringBuffer System Thread ThreadGroup ThreadLocal Throwable Triple Void Annotation Any BooleanArray " + 670 | "ByteArray Char CharArray DeprecationLevel DoubleArray Enum FloatArray Function Int IntArray Lazy " + 671 | "LazyThreadSafetyMode LongArray Nothing ShortArray Unit" 672 | ), 673 | intendSwitch: false, 674 | indentStatements: false, 675 | multiLineStrings: true, 676 | number: /^(?:0x[a-f\d_]+|0b[01_]+|(?:[\d_]+(\.\d+)?|\.\d+)(?:e[-+]?[\d_]+)?)(u|ll?|l|f)?/i, 677 | blockKeywords: words("catch class do else finally for if where try while enum"), 678 | defKeywords: words("class val var object interface fun"), 679 | atoms: words("true false null this"), 680 | hooks: { 681 | "@": function(stream) { 682 | stream.eatWhile(/[\w\$_]/); 683 | return "meta"; 684 | }, 685 | '*': function(_stream, state) { 686 | return state.prevToken == '.' ? 'variable' : 'operator'; 687 | }, 688 | '"': function(stream, state) { 689 | state.tokenize = tokenKotlinString(stream.match('""')); 690 | return state.tokenize(stream, state); 691 | }, 692 | "/": function(stream, state) { 693 | if (!stream.eat("*")) return false; 694 | state.tokenize = tokenNestedComment(1); 695 | return state.tokenize(stream, state) 696 | }, 697 | indent: function(state, ctx, textAfter, indentUnit) { 698 | var firstChar = textAfter && textAfter.charAt(0); 699 | if ((state.prevToken == "}" || state.prevToken == ")") && textAfter == "") 700 | return state.indented; 701 | if ((state.prevToken == "operator" && textAfter != "}" && state.context.type != "}") || 702 | state.prevToken == "variable" && firstChar == "." || 703 | (state.prevToken == "}" || state.prevToken == ")") && firstChar == ".") 704 | return indentUnit * 2 + ctx.indented; 705 | if (ctx.align && ctx.type == "}") 706 | return ctx.indented + (state.context.type == (textAfter || "").charAt(0) ? 0 : indentUnit); 707 | } 708 | }, 709 | modeProps: {closeBrackets: {triples: '"'}} 710 | }); 711 | 712 | def(["x-shader/x-vertex", "x-shader/x-fragment"], { 713 | name: "clike", 714 | keywords: words("sampler1D sampler2D sampler3D samplerCube " + 715 | "sampler1DShadow sampler2DShadow " + 716 | "const attribute uniform varying " + 717 | "break continue discard return " + 718 | "for while do if else struct " + 719 | "in out inout"), 720 | types: words("float int bool void " + 721 | "vec2 vec3 vec4 ivec2 ivec3 ivec4 bvec2 bvec3 bvec4 " + 722 | "mat2 mat3 mat4"), 723 | blockKeywords: words("for while do if else struct"), 724 | builtin: words("radians degrees sin cos tan asin acos atan " + 725 | "pow exp log exp2 sqrt inversesqrt " + 726 | "abs sign floor ceil fract mod min max clamp mix step smoothstep " + 727 | "length distance dot cross normalize ftransform faceforward " + 728 | "reflect refract matrixCompMult " + 729 | "lessThan lessThanEqual greaterThan greaterThanEqual " + 730 | "equal notEqual any all not " + 731 | "texture1D texture1DProj texture1DLod texture1DProjLod " + 732 | "texture2D texture2DProj texture2DLod texture2DProjLod " + 733 | "texture3D texture3DProj texture3DLod texture3DProjLod " + 734 | "textureCube textureCubeLod " + 735 | "shadow1D shadow2D shadow1DProj shadow2DProj " + 736 | "shadow1DLod shadow2DLod shadow1DProjLod shadow2DProjLod " + 737 | "dFdx dFdy fwidth " + 738 | "noise1 noise2 noise3 noise4"), 739 | atoms: words("true false " + 740 | "gl_FragColor gl_SecondaryColor gl_Normal gl_Vertex " + 741 | "gl_MultiTexCoord0 gl_MultiTexCoord1 gl_MultiTexCoord2 gl_MultiTexCoord3 " + 742 | "gl_MultiTexCoord4 gl_MultiTexCoord5 gl_MultiTexCoord6 gl_MultiTexCoord7 " + 743 | "gl_FogCoord gl_PointCoord " + 744 | "gl_Position gl_PointSize gl_ClipVertex " + 745 | "gl_FrontColor gl_BackColor gl_FrontSecondaryColor gl_BackSecondaryColor " + 746 | "gl_TexCoord gl_FogFragCoord " + 747 | "gl_FragCoord gl_FrontFacing " + 748 | "gl_FragData gl_FragDepth " + 749 | "gl_ModelViewMatrix gl_ProjectionMatrix gl_ModelViewProjectionMatrix " + 750 | "gl_TextureMatrix gl_NormalMatrix gl_ModelViewMatrixInverse " + 751 | "gl_ProjectionMatrixInverse gl_ModelViewProjectionMatrixInverse " + 752 | "gl_TextureMatrixTranspose gl_ModelViewMatrixInverseTranspose " + 753 | "gl_ProjectionMatrixInverseTranspose " + 754 | "gl_ModelViewProjectionMatrixInverseTranspose " + 755 | "gl_TextureMatrixInverseTranspose " + 756 | "gl_NormalScale gl_DepthRange gl_ClipPlane " + 757 | "gl_Point gl_FrontMaterial gl_BackMaterial gl_LightSource gl_LightModel " + 758 | "gl_FrontLightModelProduct gl_BackLightModelProduct " + 759 | "gl_TextureColor gl_EyePlaneS gl_EyePlaneT gl_EyePlaneR gl_EyePlaneQ " + 760 | "gl_FogParameters " + 761 | "gl_MaxLights gl_MaxClipPlanes gl_MaxTextureUnits gl_MaxTextureCoords " + 762 | "gl_MaxVertexAttribs gl_MaxVertexUniformComponents gl_MaxVaryingFloats " + 763 | "gl_MaxVertexTextureImageUnits gl_MaxTextureImageUnits " + 764 | "gl_MaxFragmentUniformComponents gl_MaxCombineTextureImageUnits " + 765 | "gl_MaxDrawBuffers"), 766 | indentSwitch: false, 767 | hooks: {"#": cppHook}, 768 | modeProps: {fold: ["brace", "include"]} 769 | }); 770 | 771 | def("text/x-nesc", { 772 | name: "clike", 773 | keywords: words(cKeywords + " as atomic async call command component components configuration event generic " + 774 | "implementation includes interface module new norace nx_struct nx_union post provides " + 775 | "signal task uses abstract extends"), 776 | types: cTypes, 777 | blockKeywords: words(cBlockKeywords), 778 | atoms: words("null true false"), 779 | hooks: {"#": cppHook}, 780 | modeProps: {fold: ["brace", "include"]} 781 | }); 782 | 783 | def("text/x-objectivec", { 784 | name: "clike", 785 | keywords: words(cKeywords + " " + objCKeywords), 786 | types: objCTypes, 787 | builtin: words(objCBuiltins), 788 | blockKeywords: words(cBlockKeywords + " @synthesize @try @catch @finally @autoreleasepool @synchronized"), 789 | defKeywords: words(cDefKeywords + " @interface @implementation @protocol @class"), 790 | dontIndentStatements: /^@.*$/, 791 | typeFirstDefinitions: true, 792 | atoms: words("YES NO NULL Nil nil true false nullptr"), 793 | isReservedIdentifier: cIsReservedIdentifier, 794 | hooks: { 795 | "#": cppHook, 796 | "*": pointerHook, 797 | }, 798 | modeProps: {fold: ["brace", "include"]} 799 | }); 800 | 801 | def("text/x-objectivec++", { 802 | name: "clike", 803 | keywords: words(cKeywords + " " + objCKeywords + " " + cppKeywords), 804 | types: objCTypes, 805 | builtin: words(objCBuiltins), 806 | blockKeywords: words(cBlockKeywords + " @synthesize @try @catch @finally @autoreleasepool @synchronized class try catch"), 807 | defKeywords: words(cDefKeywords + " @interface @implementation @protocol @class class namespace"), 808 | dontIndentStatements: /^@.*$|^template$/, 809 | typeFirstDefinitions: true, 810 | atoms: words("YES NO NULL Nil nil true false nullptr"), 811 | isReservedIdentifier: cIsReservedIdentifier, 812 | hooks: { 813 | "#": cppHook, 814 | "*": pointerHook, 815 | "u": cpp11StringHook, 816 | "U": cpp11StringHook, 817 | "L": cpp11StringHook, 818 | "R": cpp11StringHook, 819 | "0": cpp14Literal, 820 | "1": cpp14Literal, 821 | "2": cpp14Literal, 822 | "3": cpp14Literal, 823 | "4": cpp14Literal, 824 | "5": cpp14Literal, 825 | "6": cpp14Literal, 826 | "7": cpp14Literal, 827 | "8": cpp14Literal, 828 | "9": cpp14Literal, 829 | token: function(stream, state, style) { 830 | if (style == "variable" && stream.peek() == "(" && 831 | (state.prevToken == ";" || state.prevToken == null || 832 | state.prevToken == "}") && 833 | cppLooksLikeConstructor(stream.current())) 834 | return "def"; 835 | } 836 | }, 837 | namespaceSeparator: "::", 838 | modeProps: {fold: ["brace", "include"]} 839 | }); 840 | 841 | def("text/x-squirrel", { 842 | name: "clike", 843 | keywords: words("base break clone continue const default delete enum extends function in class" + 844 | " foreach local resume return this throw typeof yield constructor instanceof static"), 845 | types: cTypes, 846 | blockKeywords: words("case catch class else for foreach if switch try while"), 847 | defKeywords: words("function local class"), 848 | typeFirstDefinitions: true, 849 | atoms: words("true false null"), 850 | hooks: {"#": cppHook}, 851 | modeProps: {fold: ["brace", "include"]} 852 | }); 853 | 854 | // Ceylon Strings need to deal with interpolation 855 | var stringTokenizer = null; 856 | function tokenCeylonString(type) { 857 | return function(stream, state) { 858 | var escaped = false, next, end = false; 859 | while (!stream.eol()) { 860 | if (!escaped && stream.match('"') && 861 | (type == "single" || stream.match('""'))) { 862 | end = true; 863 | break; 864 | } 865 | if (!escaped && stream.match('``')) { 866 | stringTokenizer = tokenCeylonString(type); 867 | end = true; 868 | break; 869 | } 870 | next = stream.next(); 871 | escaped = type == "single" && !escaped && next == "\\"; 872 | } 873 | if (end) 874 | state.tokenize = null; 875 | return "string"; 876 | } 877 | } 878 | 879 | def("text/x-ceylon", { 880 | name: "clike", 881 | keywords: words("abstracts alias assembly assert assign break case catch class continue dynamic else" + 882 | " exists extends finally for function given if import in interface is let module new" + 883 | " nonempty object of out outer package return satisfies super switch then this throw" + 884 | " try value void while"), 885 | types: function(word) { 886 | // In Ceylon all identifiers that start with an uppercase are types 887 | var first = word.charAt(0); 888 | return (first === first.toUpperCase() && first !== first.toLowerCase()); 889 | }, 890 | blockKeywords: words("case catch class dynamic else finally for function if interface module new object switch try while"), 891 | defKeywords: words("class dynamic function interface module object package value"), 892 | builtin: words("abstract actual aliased annotation by default deprecated doc final formal late license" + 893 | " native optional sealed see serializable shared suppressWarnings tagged throws variable"), 894 | isPunctuationChar: /[\[\]{}\(\),;\:\.`]/, 895 | isOperatorChar: /[+\-*&%=<>!?|^~:\/]/, 896 | numberStart: /[\d#$]/, 897 | number: /^(?:#[\da-fA-F_]+|\$[01_]+|[\d_]+[kMGTPmunpf]?|[\d_]+\.[\d_]+(?:[eE][-+]?\d+|[kMGTPmunpf]|)|)/i, 898 | multiLineStrings: true, 899 | typeFirstDefinitions: true, 900 | atoms: words("true false null larger smaller equal empty finished"), 901 | indentSwitch: false, 902 | styleDefs: false, 903 | hooks: { 904 | "@": function(stream) { 905 | stream.eatWhile(/[\w\$_]/); 906 | return "meta"; 907 | }, 908 | '"': function(stream, state) { 909 | state.tokenize = tokenCeylonString(stream.match('""') ? "triple" : "single"); 910 | return state.tokenize(stream, state); 911 | }, 912 | '`': function(stream, state) { 913 | if (!stringTokenizer || !stream.match('`')) return false; 914 | state.tokenize = stringTokenizer; 915 | stringTokenizer = null; 916 | return state.tokenize(stream, state); 917 | }, 918 | "'": function(stream) { 919 | stream.eatWhile(/[\w\$_\xa1-\uffff]/); 920 | return "atom"; 921 | }, 922 | token: function(_stream, state, style) { 923 | if ((style == "variable" || style == "type") && 924 | state.prevToken == ".") { 925 | return "variable-2"; 926 | } 927 | } 928 | }, 929 | modeProps: { 930 | fold: ["brace", "import"], 931 | closeBrackets: {triples: '"'} 932 | } 933 | }); 934 | 935 | }); 936 | -------------------------------------------------------------------------------- /public/js/codemirror/go.js: -------------------------------------------------------------------------------- 1 | // CodeMirror, copyright (c) by Marijn Haverbeke and others 2 | // Distributed under an MIT license: https://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 | 14 | CodeMirror.defineMode("go", function(config) { 15 | var indentUnit = config.indentUnit; 16 | 17 | var keywords = { 18 | "break":true, "case":true, "chan":true, "const":true, "continue":true, 19 | "default":true, "defer":true, "else":true, "fallthrough":true, "for":true, 20 | "func":true, "go":true, "goto":true, "if":true, "import":true, 21 | "interface":true, "map":true, "package":true, "range":true, "return":true, 22 | "select":true, "struct":true, "switch":true, "type":true, "var":true, 23 | "bool":true, "byte":true, "complex64":true, "complex128":true, 24 | "float32":true, "float64":true, "int8":true, "int16":true, "int32":true, 25 | "int64":true, "string":true, "uint8":true, "uint16":true, "uint32":true, 26 | "uint64":true, "int":true, "uint":true, "uintptr":true, "error": true, 27 | "rune":true 28 | }; 29 | 30 | var atoms = { 31 | "true":true, "false":true, "iota":true, "nil":true, "append":true, 32 | "cap":true, "close":true, "complex":true, "copy":true, "delete":true, "imag":true, 33 | "len":true, "make":true, "new":true, "panic":true, "print":true, 34 | "println":true, "real":true, "recover":true 35 | }; 36 | 37 | var isOperatorChar = /[+\-*&^%:=<>!|\/]/; 38 | 39 | var curPunc; 40 | 41 | function tokenBase(stream, state) { 42 | var ch = stream.next(); 43 | if (ch == '"' || ch == "'" || ch == "`") { 44 | state.tokenize = tokenString(ch); 45 | return state.tokenize(stream, state); 46 | } 47 | if (/[\d\.]/.test(ch)) { 48 | if (ch == ".") { 49 | stream.match(/^[0-9]+([eE][\-+]?[0-9]+)?/); 50 | } else if (ch == "0") { 51 | stream.match(/^[xX][0-9a-fA-F]+/) || stream.match(/^0[0-7]+/); 52 | } else { 53 | stream.match(/^[0-9]*\.?[0-9]*([eE][\-+]?[0-9]+)?/); 54 | } 55 | return "number"; 56 | } 57 | if (/[\[\]{}\(\),;\:\.]/.test(ch)) { 58 | curPunc = ch; 59 | return null; 60 | } 61 | if (ch == "/") { 62 | if (stream.eat("*")) { 63 | state.tokenize = tokenComment; 64 | return tokenComment(stream, state); 65 | } 66 | if (stream.eat("/")) { 67 | stream.skipToEnd(); 68 | return "comment"; 69 | } 70 | } 71 | if (isOperatorChar.test(ch)) { 72 | stream.eatWhile(isOperatorChar); 73 | return "operator"; 74 | } 75 | stream.eatWhile(/[\w\$_\xa1-\uffff]/); 76 | var cur = stream.current(); 77 | if (keywords.propertyIsEnumerable(cur)) { 78 | if (cur == "case" || cur == "default") curPunc = "case"; 79 | return "keyword"; 80 | } 81 | if (atoms.propertyIsEnumerable(cur)) return "atom"; 82 | return "variable"; 83 | } 84 | 85 | function tokenString(quote) { 86 | return function(stream, state) { 87 | var escaped = false, next, end = false; 88 | while ((next = stream.next()) != null) { 89 | if (next == quote && !escaped) {end = true; break;} 90 | escaped = !escaped && quote != "`" && next == "\\"; 91 | } 92 | if (end || !(escaped || quote == "`")) 93 | state.tokenize = tokenBase; 94 | return "string"; 95 | }; 96 | } 97 | 98 | function tokenComment(stream, state) { 99 | var maybeEnd = false, ch; 100 | while (ch = stream.next()) { 101 | if (ch == "/" && maybeEnd) { 102 | state.tokenize = tokenBase; 103 | break; 104 | } 105 | maybeEnd = (ch == "*"); 106 | } 107 | return "comment"; 108 | } 109 | 110 | function Context(indented, column, type, align, prev) { 111 | this.indented = indented; 112 | this.column = column; 113 | this.type = type; 114 | this.align = align; 115 | this.prev = prev; 116 | } 117 | function pushContext(state, col, type) { 118 | return state.context = new Context(state.indented, col, type, null, state.context); 119 | } 120 | function popContext(state) { 121 | if (!state.context.prev) return; 122 | var t = state.context.type; 123 | if (t == ")" || t == "]" || t == "}") 124 | state.indented = state.context.indented; 125 | return state.context = state.context.prev; 126 | } 127 | 128 | // Interface 129 | 130 | return { 131 | startState: function(basecolumn) { 132 | return { 133 | tokenize: null, 134 | context: new Context((basecolumn || 0) - indentUnit, 0, "top", false), 135 | indented: 0, 136 | startOfLine: true 137 | }; 138 | }, 139 | 140 | token: function(stream, state) { 141 | var ctx = state.context; 142 | if (stream.sol()) { 143 | if (ctx.align == null) ctx.align = false; 144 | state.indented = stream.indentation(); 145 | state.startOfLine = true; 146 | if (ctx.type == "case") ctx.type = "}"; 147 | } 148 | if (stream.eatSpace()) return null; 149 | curPunc = null; 150 | var style = (state.tokenize || tokenBase)(stream, state); 151 | if (style == "comment") return style; 152 | if (ctx.align == null) ctx.align = true; 153 | 154 | if (curPunc == "{") pushContext(state, stream.column(), "}"); 155 | else if (curPunc == "[") pushContext(state, stream.column(), "]"); 156 | else if (curPunc == "(") pushContext(state, stream.column(), ")"); 157 | else if (curPunc == "case") ctx.type = "case"; 158 | else if (curPunc == "}" && ctx.type == "}") popContext(state); 159 | else if (curPunc == ctx.type) popContext(state); 160 | state.startOfLine = false; 161 | return style; 162 | }, 163 | 164 | indent: function(state, textAfter) { 165 | if (state.tokenize != tokenBase && state.tokenize != null) return CodeMirror.Pass; 166 | var ctx = state.context, firstChar = textAfter && textAfter.charAt(0); 167 | if (ctx.type == "case" && /^(?:case|default)\b/.test(textAfter)) { 168 | state.context.type = "}"; 169 | return ctx.indented; 170 | } 171 | var closing = firstChar == ctx.type; 172 | if (ctx.align) return ctx.column + (closing ? 0 : 1); 173 | else return ctx.indented + (closing ? 0 : indentUnit); 174 | }, 175 | 176 | electricChars: "{}):", 177 | closeBrackets: "()[]{}''\"\"``", 178 | fold: "brace", 179 | blockCommentStart: "/*", 180 | blockCommentEnd: "*/", 181 | lineComment: "//" 182 | }; 183 | }); 184 | 185 | CodeMirror.defineMIME("text/x-go", "go"); 186 | 187 | }); 188 | -------------------------------------------------------------------------------- /public/js/codemirror/javascript-hint.js: -------------------------------------------------------------------------------- 1 | // CodeMirror, copyright (c) by Marijn Haverbeke and others 2 | // Distributed under an MIT license: https://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 | var Pos = CodeMirror.Pos; 13 | 14 | function forEach(arr, f) { 15 | for (var i = 0, e = arr.length; i < e; ++i) f(arr[i]); 16 | } 17 | 18 | function arrayContains(arr, item) { 19 | if (!Array.prototype.indexOf) { 20 | var i = arr.length; 21 | while (i--) { 22 | if (arr[i] === item) { 23 | return true; 24 | } 25 | } 26 | return false; 27 | } 28 | return arr.indexOf(item) != -1; 29 | } 30 | 31 | function scriptHint(editor, keywords, getToken, options) { 32 | // Find the token at the cursor 33 | var cur = editor.getCursor(), token = getToken(editor, cur); 34 | if (/\b(?:string|comment)\b/.test(token.type)) return; 35 | var innerMode = CodeMirror.innerMode(editor.getMode(), token.state); 36 | if (innerMode.mode.helperType === "json") return; 37 | token.state = innerMode.state; 38 | 39 | // If it's not a 'word-style' token, ignore the token. 40 | if (!/^[\w$_]*$/.test(token.string)) { 41 | token = {start: cur.ch, end: cur.ch, string: "", state: token.state, 42 | type: token.string == "." ? "property" : null}; 43 | } else if (token.end > cur.ch) { 44 | token.end = cur.ch; 45 | token.string = token.string.slice(0, cur.ch - token.start); 46 | } 47 | 48 | var tprop = token; 49 | // If it is a property, find out what it is a property of. 50 | while (tprop.type == "property") { 51 | tprop = getToken(editor, Pos(cur.line, tprop.start)); 52 | if (tprop.string != ".") return; 53 | tprop = getToken(editor, Pos(cur.line, tprop.start)); 54 | if (!context) var context = []; 55 | context.push(tprop); 56 | } 57 | return {list: getCompletions(token, context, keywords, options), 58 | from: Pos(cur.line, token.start), 59 | to: Pos(cur.line, token.end)}; 60 | } 61 | 62 | function javascriptHint(editor, options) { 63 | return scriptHint(editor, javascriptKeywords, 64 | function (e, cur) {return e.getTokenAt(cur);}, 65 | options); 66 | }; 67 | CodeMirror.registerHelper("hint", "javascript", javascriptHint); 68 | 69 | function getCoffeeScriptToken(editor, cur) { 70 | // This getToken, it is for coffeescript, imitates the behavior of 71 | // getTokenAt method in javascript.js, that is, returning "property" 72 | // type and treat "." as independent token. 73 | var token = editor.getTokenAt(cur); 74 | if (cur.ch == token.start + 1 && token.string.charAt(0) == '.') { 75 | token.end = token.start; 76 | token.string = '.'; 77 | token.type = "property"; 78 | } 79 | else if (/^\.[\w$_]*$/.test(token.string)) { 80 | token.type = "property"; 81 | token.start++; 82 | token.string = token.string.replace(/\./, ''); 83 | } 84 | return token; 85 | } 86 | 87 | function coffeescriptHint(editor, options) { 88 | return scriptHint(editor, coffeescriptKeywords, getCoffeeScriptToken, options); 89 | } 90 | CodeMirror.registerHelper("hint", "coffeescript", coffeescriptHint); 91 | 92 | var stringProps = ("charAt charCodeAt indexOf lastIndexOf substring substr slice trim trimLeft trimRight " + 93 | "toUpperCase toLowerCase split concat match replace search").split(" "); 94 | var arrayProps = ("length concat join splice push pop shift unshift slice reverse sort indexOf " + 95 | "lastIndexOf every some filter forEach map reduce reduceRight ").split(" "); 96 | var funcProps = "prototype apply call bind".split(" "); 97 | var javascriptKeywords = ("break case catch class const continue debugger default delete do else export extends false finally for function " + 98 | "if in import instanceof new null return super switch this throw true try typeof var void while with yield").split(" "); 99 | var coffeescriptKeywords = ("and break catch class continue delete do else extends false finally for " + 100 | "if in instanceof isnt new no not null of off on or return switch then throw true try typeof until void while with yes").split(" "); 101 | 102 | function forAllProps(obj, callback) { 103 | if (!Object.getOwnPropertyNames || !Object.getPrototypeOf) { 104 | for (var name in obj) callback(name) 105 | } else { 106 | for (var o = obj; o; o = Object.getPrototypeOf(o)) 107 | Object.getOwnPropertyNames(o).forEach(callback) 108 | } 109 | } 110 | 111 | function getCompletions(token, context, keywords, options) { 112 | var found = [], start = token.string, global = options && options.globalScope || window; 113 | function maybeAdd(str) { 114 | if (str.lastIndexOf(start, 0) == 0 && !arrayContains(found, str)) found.push(str); 115 | } 116 | function gatherCompletions(obj) { 117 | if (typeof obj == "string") forEach(stringProps, maybeAdd); 118 | else if (obj instanceof Array) forEach(arrayProps, maybeAdd); 119 | else if (obj instanceof Function) forEach(funcProps, maybeAdd); 120 | forAllProps(obj, maybeAdd) 121 | } 122 | 123 | if (context && context.length) { 124 | // If this is a property, see if it belongs to some object we can 125 | // find in the current environment. 126 | var obj = context.pop(), base; 127 | if (obj.type && obj.type.indexOf("variable") === 0) { 128 | if (options && options.additionalContext) 129 | base = options.additionalContext[obj.string]; 130 | if (!options || options.useGlobalScope !== false) 131 | base = base || global[obj.string]; 132 | } else if (obj.type == "string") { 133 | base = ""; 134 | } else if (obj.type == "atom") { 135 | base = 1; 136 | } else if (obj.type == "function") { 137 | if (global.jQuery != null && (obj.string == '$' || obj.string == 'jQuery') && 138 | (typeof global.jQuery == 'function')) 139 | base = global.jQuery(); 140 | else if (global._ != null && (obj.string == '_') && (typeof global._ == 'function')) 141 | base = global._(); 142 | } 143 | while (base != null && context.length) 144 | base = base[context.pop().string]; 145 | if (base != null) gatherCompletions(base); 146 | } else { 147 | // If not, just look in the global object, any local scope, and optional additional-context 148 | // (reading into JS mode internals to get at the local and global variables) 149 | for (var v = token.state.localVars; v; v = v.next) maybeAdd(v.name); 150 | for (var c = token.state.context; c; c = c.prev) 151 | for (var v = c.vars; v; v = v.next) maybeAdd(v.name) 152 | for (var v = token.state.globalVars; v; v = v.next) maybeAdd(v.name); 153 | if (options && options.additionalContext != null) 154 | for (var key in options.additionalContext) 155 | maybeAdd(key); 156 | if (!options || options.useGlobalScope !== false) 157 | gatherCompletions(global); 158 | forEach(keywords, maybeAdd); 159 | } 160 | return found; 161 | } 162 | }); 163 | -------------------------------------------------------------------------------- /public/js/codemirror/javascript.js: -------------------------------------------------------------------------------- 1 | // CodeMirror, copyright (c) by Marijn Haverbeke and others 2 | // Distributed under an MIT license: https://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 | 14 | CodeMirror.defineMode("javascript", function(config, parserConfig) { 15 | var indentUnit = config.indentUnit; 16 | var statementIndent = parserConfig.statementIndent; 17 | var jsonldMode = parserConfig.jsonld; 18 | var jsonMode = parserConfig.json || jsonldMode; 19 | var trackScope = parserConfig.trackScope !== false 20 | var isTS = parserConfig.typescript; 21 | var wordRE = parserConfig.wordCharacters || /[\w$\xa1-\uffff]/; 22 | 23 | // Tokenizer 24 | 25 | var keywords = function(){ 26 | function kw(type) {return {type: type, style: "keyword"};} 27 | var A = kw("keyword a"), B = kw("keyword b"), C = kw("keyword c"), D = kw("keyword d"); 28 | var operator = kw("operator"), atom = {type: "atom", style: "atom"}; 29 | 30 | return { 31 | "if": kw("if"), "while": A, "with": A, "else": B, "do": B, "try": B, "finally": B, 32 | "return": D, "break": D, "continue": D, "new": kw("new"), "delete": C, "void": C, "throw": C, 33 | "debugger": kw("debugger"), "var": kw("var"), "const": kw("var"), "let": kw("var"), 34 | "function": kw("function"), "catch": kw("catch"), 35 | "for": kw("for"), "switch": kw("switch"), "case": kw("case"), "default": kw("default"), 36 | "in": operator, "typeof": operator, "instanceof": operator, 37 | "true": atom, "false": atom, "null": atom, "undefined": atom, "NaN": atom, "Infinity": atom, 38 | "this": kw("this"), "class": kw("class"), "super": kw("atom"), 39 | "yield": C, "export": kw("export"), "import": kw("import"), "extends": C, 40 | "await": C 41 | }; 42 | }(); 43 | 44 | var isOperatorChar = /[+\-*&%=<>!?|~^@]/; 45 | var isJsonldKeyword = /^@(context|id|value|language|type|container|list|set|reverse|index|base|vocab|graph)"/; 46 | 47 | function readRegexp(stream) { 48 | var escaped = false, next, inSet = false; 49 | while ((next = stream.next()) != null) { 50 | if (!escaped) { 51 | if (next == "/" && !inSet) return; 52 | if (next == "[") inSet = true; 53 | else if (inSet && next == "]") inSet = false; 54 | } 55 | escaped = !escaped && next == "\\"; 56 | } 57 | } 58 | 59 | // Used as scratch variables to communicate multiple values without 60 | // consing up tons of objects. 61 | var type, content; 62 | function ret(tp, style, cont) { 63 | type = tp; content = cont; 64 | return style; 65 | } 66 | function tokenBase(stream, state) { 67 | var ch = stream.next(); 68 | if (ch == '"' || ch == "'") { 69 | state.tokenize = tokenString(ch); 70 | return state.tokenize(stream, state); 71 | } else if (ch == "." && stream.match(/^\d[\d_]*(?:[eE][+\-]?[\d_]+)?/)) { 72 | return ret("number", "number"); 73 | } else if (ch == "." && stream.match("..")) { 74 | return ret("spread", "meta"); 75 | } else if (/[\[\]{}\(\),;\:\.]/.test(ch)) { 76 | return ret(ch); 77 | } else if (ch == "=" && stream.eat(">")) { 78 | return ret("=>", "operator"); 79 | } else if (ch == "0" && stream.match(/^(?:x[\dA-Fa-f_]+|o[0-7_]+|b[01_]+)n?/)) { 80 | return ret("number", "number"); 81 | } else if (/\d/.test(ch)) { 82 | stream.match(/^[\d_]*(?:n|(?:\.[\d_]*)?(?:[eE][+\-]?[\d_]+)?)?/); 83 | return ret("number", "number"); 84 | } else if (ch == "/") { 85 | if (stream.eat("*")) { 86 | state.tokenize = tokenComment; 87 | return tokenComment(stream, state); 88 | } else if (stream.eat("/")) { 89 | stream.skipToEnd(); 90 | return ret("comment", "comment"); 91 | } else if (expressionAllowed(stream, state, 1)) { 92 | readRegexp(stream); 93 | stream.match(/^\b(([gimyus])(?![gimyus]*\2))+\b/); 94 | return ret("regexp", "string-2"); 95 | } else { 96 | stream.eat("="); 97 | return ret("operator", "operator", stream.current()); 98 | } 99 | } else if (ch == "`") { 100 | state.tokenize = tokenQuasi; 101 | return tokenQuasi(stream, state); 102 | } else if (ch == "#" && stream.peek() == "!") { 103 | stream.skipToEnd(); 104 | return ret("meta", "meta"); 105 | } else if (ch == "#" && stream.eatWhile(wordRE)) { 106 | return ret("variable", "property") 107 | } else if (ch == "<" && stream.match("!--") || 108 | (ch == "-" && stream.match("->") && !/\S/.test(stream.string.slice(0, stream.start)))) { 109 | stream.skipToEnd() 110 | return ret("comment", "comment") 111 | } else if (isOperatorChar.test(ch)) { 112 | if (ch != ">" || !state.lexical || state.lexical.type != ">") { 113 | if (stream.eat("=")) { 114 | if (ch == "!" || ch == "=") stream.eat("=") 115 | } else if (/[<>*+\-|&?]/.test(ch)) { 116 | stream.eat(ch) 117 | if (ch == ">") stream.eat(ch) 118 | } 119 | } 120 | if (ch == "?" && stream.eat(".")) return ret(".") 121 | return ret("operator", "operator", stream.current()); 122 | } else if (wordRE.test(ch)) { 123 | stream.eatWhile(wordRE); 124 | var word = stream.current() 125 | if (state.lastType != ".") { 126 | if (keywords.propertyIsEnumerable(word)) { 127 | var kw = keywords[word] 128 | return ret(kw.type, kw.style, word) 129 | } 130 | if (word == "async" && stream.match(/^(\s|\/\*([^*]|\*(?!\/))*?\*\/)*[\[\(\w]/, false)) 131 | return ret("async", "keyword", word) 132 | } 133 | return ret("variable", "variable", word) 134 | } 135 | } 136 | 137 | function tokenString(quote) { 138 | return function(stream, state) { 139 | var escaped = false, next; 140 | if (jsonldMode && stream.peek() == "@" && stream.match(isJsonldKeyword)){ 141 | state.tokenize = tokenBase; 142 | return ret("jsonld-keyword", "meta"); 143 | } 144 | while ((next = stream.next()) != null) { 145 | if (next == quote && !escaped) break; 146 | escaped = !escaped && next == "\\"; 147 | } 148 | if (!escaped) state.tokenize = tokenBase; 149 | return ret("string", "string"); 150 | }; 151 | } 152 | 153 | function tokenComment(stream, state) { 154 | var maybeEnd = false, ch; 155 | while (ch = stream.next()) { 156 | if (ch == "/" && maybeEnd) { 157 | state.tokenize = tokenBase; 158 | break; 159 | } 160 | maybeEnd = (ch == "*"); 161 | } 162 | return ret("comment", "comment"); 163 | } 164 | 165 | function tokenQuasi(stream, state) { 166 | var escaped = false, next; 167 | while ((next = stream.next()) != null) { 168 | if (!escaped && (next == "`" || next == "$" && stream.eat("{"))) { 169 | state.tokenize = tokenBase; 170 | break; 171 | } 172 | escaped = !escaped && next == "\\"; 173 | } 174 | return ret("quasi", "string-2", stream.current()); 175 | } 176 | 177 | var brackets = "([{}])"; 178 | // This is a crude lookahead trick to try and notice that we're 179 | // parsing the argument patterns for a fat-arrow function before we 180 | // actually hit the arrow token. It only works if the arrow is on 181 | // the same line as the arguments and there's no strange noise 182 | // (comments) in between. Fallback is to only notice when we hit the 183 | // arrow, and not declare the arguments as locals for the arrow 184 | // body. 185 | function findFatArrow(stream, state) { 186 | if (state.fatArrowAt) state.fatArrowAt = null; 187 | var arrow = stream.string.indexOf("=>", stream.start); 188 | if (arrow < 0) return; 189 | 190 | if (isTS) { // Try to skip TypeScript return type declarations after the arguments 191 | var m = /:\s*(?:\w+(?:<[^>]*>|\[\])?|\{[^}]*\})\s*$/.exec(stream.string.slice(stream.start, arrow)) 192 | if (m) arrow = m.index 193 | } 194 | 195 | var depth = 0, sawSomething = false; 196 | for (var pos = arrow - 1; pos >= 0; --pos) { 197 | var ch = stream.string.charAt(pos); 198 | var bracket = brackets.indexOf(ch); 199 | if (bracket >= 0 && bracket < 3) { 200 | if (!depth) { ++pos; break; } 201 | if (--depth == 0) { if (ch == "(") sawSomething = true; break; } 202 | } else if (bracket >= 3 && bracket < 6) { 203 | ++depth; 204 | } else if (wordRE.test(ch)) { 205 | sawSomething = true; 206 | } else if (/["'\/`]/.test(ch)) { 207 | for (;; --pos) { 208 | if (pos == 0) return 209 | var next = stream.string.charAt(pos - 1) 210 | if (next == ch && stream.string.charAt(pos - 2) != "\\") { pos--; break } 211 | } 212 | } else if (sawSomething && !depth) { 213 | ++pos; 214 | break; 215 | } 216 | } 217 | if (sawSomething && !depth) state.fatArrowAt = pos; 218 | } 219 | 220 | // Parser 221 | 222 | var atomicTypes = {"atom": true, "number": true, "variable": true, "string": true, 223 | "regexp": true, "this": true, "import": true, "jsonld-keyword": true}; 224 | 225 | function JSLexical(indented, column, type, align, prev, info) { 226 | this.indented = indented; 227 | this.column = column; 228 | this.type = type; 229 | this.prev = prev; 230 | this.info = info; 231 | if (align != null) this.align = align; 232 | } 233 | 234 | function inScope(state, varname) { 235 | if (!trackScope) return false 236 | for (var v = state.localVars; v; v = v.next) 237 | if (v.name == varname) return true; 238 | for (var cx = state.context; cx; cx = cx.prev) { 239 | for (var v = cx.vars; v; v = v.next) 240 | if (v.name == varname) return true; 241 | } 242 | } 243 | 244 | function parseJS(state, style, type, content, stream) { 245 | var cc = state.cc; 246 | // Communicate our context to the combinators. 247 | // (Less wasteful than consing up a hundred closures on every call.) 248 | cx.state = state; cx.stream = stream; cx.marked = null, cx.cc = cc; cx.style = style; 249 | 250 | if (!state.lexical.hasOwnProperty("align")) 251 | state.lexical.align = true; 252 | 253 | while(true) { 254 | var combinator = cc.length ? cc.pop() : jsonMode ? expression : statement; 255 | if (combinator(type, content)) { 256 | while(cc.length && cc[cc.length - 1].lex) 257 | cc.pop()(); 258 | if (cx.marked) return cx.marked; 259 | if (type == "variable" && inScope(state, content)) return "variable-2"; 260 | return style; 261 | } 262 | } 263 | } 264 | 265 | // Combinator utils 266 | 267 | var cx = {state: null, column: null, marked: null, cc: null}; 268 | function pass() { 269 | for (var i = arguments.length - 1; i >= 0; i--) cx.cc.push(arguments[i]); 270 | } 271 | function cont() { 272 | pass.apply(null, arguments); 273 | return true; 274 | } 275 | function inList(name, list) { 276 | for (var v = list; v; v = v.next) if (v.name == name) return true 277 | return false; 278 | } 279 | function register(varname) { 280 | var state = cx.state; 281 | cx.marked = "def"; 282 | if (!trackScope) return 283 | if (state.context) { 284 | if (state.lexical.info == "var" && state.context && state.context.block) { 285 | // FIXME function decls are also not block scoped 286 | var newContext = registerVarScoped(varname, state.context) 287 | if (newContext != null) { 288 | state.context = newContext 289 | return 290 | } 291 | } else if (!inList(varname, state.localVars)) { 292 | state.localVars = new Var(varname, state.localVars) 293 | return 294 | } 295 | } 296 | // Fall through means this is global 297 | if (parserConfig.globalVars && !inList(varname, state.globalVars)) 298 | state.globalVars = new Var(varname, state.globalVars) 299 | } 300 | function registerVarScoped(varname, context) { 301 | if (!context) { 302 | return null 303 | } else if (context.block) { 304 | var inner = registerVarScoped(varname, context.prev) 305 | if (!inner) return null 306 | if (inner == context.prev) return context 307 | return new Context(inner, context.vars, true) 308 | } else if (inList(varname, context.vars)) { 309 | return context 310 | } else { 311 | return new Context(context.prev, new Var(varname, context.vars), false) 312 | } 313 | } 314 | 315 | function isModifier(name) { 316 | return name == "public" || name == "private" || name == "protected" || name == "abstract" || name == "readonly" 317 | } 318 | 319 | // Combinators 320 | 321 | function Context(prev, vars, block) { this.prev = prev; this.vars = vars; this.block = block } 322 | function Var(name, next) { this.name = name; this.next = next } 323 | 324 | var defaultVars = new Var("this", new Var("arguments", null)) 325 | function pushcontext() { 326 | cx.state.context = new Context(cx.state.context, cx.state.localVars, false) 327 | cx.state.localVars = defaultVars 328 | } 329 | function pushblockcontext() { 330 | cx.state.context = new Context(cx.state.context, cx.state.localVars, true) 331 | cx.state.localVars = null 332 | } 333 | pushcontext.lex = pushblockcontext.lex = true 334 | function popcontext() { 335 | cx.state.localVars = cx.state.context.vars 336 | cx.state.context = cx.state.context.prev 337 | } 338 | popcontext.lex = true 339 | function pushlex(type, info) { 340 | var result = function() { 341 | var state = cx.state, indent = state.indented; 342 | if (state.lexical.type == "stat") indent = state.lexical.indented; 343 | else for (var outer = state.lexical; outer && outer.type == ")" && outer.align; outer = outer.prev) 344 | indent = outer.indented; 345 | state.lexical = new JSLexical(indent, cx.stream.column(), type, null, state.lexical, info); 346 | }; 347 | result.lex = true; 348 | return result; 349 | } 350 | function poplex() { 351 | var state = cx.state; 352 | if (state.lexical.prev) { 353 | if (state.lexical.type == ")") 354 | state.indented = state.lexical.indented; 355 | state.lexical = state.lexical.prev; 356 | } 357 | } 358 | poplex.lex = true; 359 | 360 | function expect(wanted) { 361 | function exp(type) { 362 | if (type == wanted) return cont(); 363 | else if (wanted == ";" || type == "}" || type == ")" || type == "]") return pass(); 364 | else return cont(exp); 365 | }; 366 | return exp; 367 | } 368 | 369 | function statement(type, value) { 370 | if (type == "var") return cont(pushlex("vardef", value), vardef, expect(";"), poplex); 371 | if (type == "keyword a") return cont(pushlex("form"), parenExpr, statement, poplex); 372 | if (type == "keyword b") return cont(pushlex("form"), statement, poplex); 373 | if (type == "keyword d") return cx.stream.match(/^\s*$/, false) ? cont() : cont(pushlex("stat"), maybeexpression, expect(";"), poplex); 374 | if (type == "debugger") return cont(expect(";")); 375 | if (type == "{") return cont(pushlex("}"), pushblockcontext, block, poplex, popcontext); 376 | if (type == ";") return cont(); 377 | if (type == "if") { 378 | if (cx.state.lexical.info == "else" && cx.state.cc[cx.state.cc.length - 1] == poplex) 379 | cx.state.cc.pop()(); 380 | return cont(pushlex("form"), parenExpr, statement, poplex, maybeelse); 381 | } 382 | if (type == "function") return cont(functiondef); 383 | if (type == "for") return cont(pushlex("form"), pushblockcontext, forspec, statement, popcontext, poplex); 384 | if (type == "class" || (isTS && value == "interface")) { 385 | cx.marked = "keyword" 386 | return cont(pushlex("form", type == "class" ? type : value), className, poplex) 387 | } 388 | if (type == "variable") { 389 | if (isTS && value == "declare") { 390 | cx.marked = "keyword" 391 | return cont(statement) 392 | } else if (isTS && (value == "module" || value == "enum" || value == "type") && cx.stream.match(/^\s*\w/, false)) { 393 | cx.marked = "keyword" 394 | if (value == "enum") return cont(enumdef); 395 | else if (value == "type") return cont(typename, expect("operator"), typeexpr, expect(";")); 396 | else return cont(pushlex("form"), pattern, expect("{"), pushlex("}"), block, poplex, poplex) 397 | } else if (isTS && value == "namespace") { 398 | cx.marked = "keyword" 399 | return cont(pushlex("form"), expression, statement, poplex) 400 | } else if (isTS && value == "abstract") { 401 | cx.marked = "keyword" 402 | return cont(statement) 403 | } else { 404 | return cont(pushlex("stat"), maybelabel); 405 | } 406 | } 407 | if (type == "switch") return cont(pushlex("form"), parenExpr, expect("{"), pushlex("}", "switch"), pushblockcontext, 408 | block, poplex, poplex, popcontext); 409 | if (type == "case") return cont(expression, expect(":")); 410 | if (type == "default") return cont(expect(":")); 411 | if (type == "catch") return cont(pushlex("form"), pushcontext, maybeCatchBinding, statement, poplex, popcontext); 412 | if (type == "export") return cont(pushlex("stat"), afterExport, poplex); 413 | if (type == "import") return cont(pushlex("stat"), afterImport, poplex); 414 | if (type == "async") return cont(statement) 415 | if (value == "@") return cont(expression, statement) 416 | return pass(pushlex("stat"), expression, expect(";"), poplex); 417 | } 418 | function maybeCatchBinding(type) { 419 | if (type == "(") return cont(funarg, expect(")")) 420 | } 421 | function expression(type, value) { 422 | return expressionInner(type, value, false); 423 | } 424 | function expressionNoComma(type, value) { 425 | return expressionInner(type, value, true); 426 | } 427 | function parenExpr(type) { 428 | if (type != "(") return pass() 429 | return cont(pushlex(")"), maybeexpression, expect(")"), poplex) 430 | } 431 | function expressionInner(type, value, noComma) { 432 | if (cx.state.fatArrowAt == cx.stream.start) { 433 | var body = noComma ? arrowBodyNoComma : arrowBody; 434 | if (type == "(") return cont(pushcontext, pushlex(")"), commasep(funarg, ")"), poplex, expect("=>"), body, popcontext); 435 | else if (type == "variable") return pass(pushcontext, pattern, expect("=>"), body, popcontext); 436 | } 437 | 438 | var maybeop = noComma ? maybeoperatorNoComma : maybeoperatorComma; 439 | if (atomicTypes.hasOwnProperty(type)) return cont(maybeop); 440 | if (type == "function") return cont(functiondef, maybeop); 441 | if (type == "class" || (isTS && value == "interface")) { cx.marked = "keyword"; return cont(pushlex("form"), classExpression, poplex); } 442 | if (type == "keyword c" || type == "async") return cont(noComma ? expressionNoComma : expression); 443 | if (type == "(") return cont(pushlex(")"), maybeexpression, expect(")"), poplex, maybeop); 444 | if (type == "operator" || type == "spread") return cont(noComma ? expressionNoComma : expression); 445 | if (type == "[") return cont(pushlex("]"), arrayLiteral, poplex, maybeop); 446 | if (type == "{") return contCommasep(objprop, "}", null, maybeop); 447 | if (type == "quasi") return pass(quasi, maybeop); 448 | if (type == "new") return cont(maybeTarget(noComma)); 449 | return cont(); 450 | } 451 | function maybeexpression(type) { 452 | if (type.match(/[;\}\)\],]/)) return pass(); 453 | return pass(expression); 454 | } 455 | 456 | function maybeoperatorComma(type, value) { 457 | if (type == ",") return cont(maybeexpression); 458 | return maybeoperatorNoComma(type, value, false); 459 | } 460 | function maybeoperatorNoComma(type, value, noComma) { 461 | var me = noComma == false ? maybeoperatorComma : maybeoperatorNoComma; 462 | var expr = noComma == false ? expression : expressionNoComma; 463 | if (type == "=>") return cont(pushcontext, noComma ? arrowBodyNoComma : arrowBody, popcontext); 464 | if (type == "operator") { 465 | if (/\+\+|--/.test(value) || isTS && value == "!") return cont(me); 466 | if (isTS && value == "<" && cx.stream.match(/^([^<>]|<[^<>]*>)*>\s*\(/, false)) 467 | return cont(pushlex(">"), commasep(typeexpr, ">"), poplex, me); 468 | if (value == "?") return cont(expression, expect(":"), expr); 469 | return cont(expr); 470 | } 471 | if (type == "quasi") { return pass(quasi, me); } 472 | if (type == ";") return; 473 | if (type == "(") return contCommasep(expressionNoComma, ")", "call", me); 474 | if (type == ".") return cont(property, me); 475 | if (type == "[") return cont(pushlex("]"), maybeexpression, expect("]"), poplex, me); 476 | if (isTS && value == "as") { cx.marked = "keyword"; return cont(typeexpr, me) } 477 | if (type == "regexp") { 478 | cx.state.lastType = cx.marked = "operator" 479 | cx.stream.backUp(cx.stream.pos - cx.stream.start - 1) 480 | return cont(expr) 481 | } 482 | } 483 | function quasi(type, value) { 484 | if (type != "quasi") return pass(); 485 | if (value.slice(value.length - 2) != "${") return cont(quasi); 486 | return cont(maybeexpression, continueQuasi); 487 | } 488 | function continueQuasi(type) { 489 | if (type == "}") { 490 | cx.marked = "string-2"; 491 | cx.state.tokenize = tokenQuasi; 492 | return cont(quasi); 493 | } 494 | } 495 | function arrowBody(type) { 496 | findFatArrow(cx.stream, cx.state); 497 | return pass(type == "{" ? statement : expression); 498 | } 499 | function arrowBodyNoComma(type) { 500 | findFatArrow(cx.stream, cx.state); 501 | return pass(type == "{" ? statement : expressionNoComma); 502 | } 503 | function maybeTarget(noComma) { 504 | return function(type) { 505 | if (type == ".") return cont(noComma ? targetNoComma : target); 506 | else if (type == "variable" && isTS) return cont(maybeTypeArgs, noComma ? maybeoperatorNoComma : maybeoperatorComma) 507 | else return pass(noComma ? expressionNoComma : expression); 508 | }; 509 | } 510 | function target(_, value) { 511 | if (value == "target") { cx.marked = "keyword"; return cont(maybeoperatorComma); } 512 | } 513 | function targetNoComma(_, value) { 514 | if (value == "target") { cx.marked = "keyword"; return cont(maybeoperatorNoComma); } 515 | } 516 | function maybelabel(type) { 517 | if (type == ":") return cont(poplex, statement); 518 | return pass(maybeoperatorComma, expect(";"), poplex); 519 | } 520 | function property(type) { 521 | if (type == "variable") {cx.marked = "property"; return cont();} 522 | } 523 | function objprop(type, value) { 524 | if (type == "async") { 525 | cx.marked = "property"; 526 | return cont(objprop); 527 | } else if (type == "variable" || cx.style == "keyword") { 528 | cx.marked = "property"; 529 | if (value == "get" || value == "set") return cont(getterSetter); 530 | var m // Work around fat-arrow-detection complication for detecting typescript typed arrow params 531 | if (isTS && cx.state.fatArrowAt == cx.stream.start && (m = cx.stream.match(/^\s*:\s*/, false))) 532 | cx.state.fatArrowAt = cx.stream.pos + m[0].length 533 | return cont(afterprop); 534 | } else if (type == "number" || type == "string") { 535 | cx.marked = jsonldMode ? "property" : (cx.style + " property"); 536 | return cont(afterprop); 537 | } else if (type == "jsonld-keyword") { 538 | return cont(afterprop); 539 | } else if (isTS && isModifier(value)) { 540 | cx.marked = "keyword" 541 | return cont(objprop) 542 | } else if (type == "[") { 543 | return cont(expression, maybetype, expect("]"), afterprop); 544 | } else if (type == "spread") { 545 | return cont(expressionNoComma, afterprop); 546 | } else if (value == "*") { 547 | cx.marked = "keyword"; 548 | return cont(objprop); 549 | } else if (type == ":") { 550 | return pass(afterprop) 551 | } 552 | } 553 | function getterSetter(type) { 554 | if (type != "variable") return pass(afterprop); 555 | cx.marked = "property"; 556 | return cont(functiondef); 557 | } 558 | function afterprop(type) { 559 | if (type == ":") return cont(expressionNoComma); 560 | if (type == "(") return pass(functiondef); 561 | } 562 | function commasep(what, end, sep) { 563 | function proceed(type, value) { 564 | if (sep ? sep.indexOf(type) > -1 : type == ",") { 565 | var lex = cx.state.lexical; 566 | if (lex.info == "call") lex.pos = (lex.pos || 0) + 1; 567 | return cont(function(type, value) { 568 | if (type == end || value == end) return pass() 569 | return pass(what) 570 | }, proceed); 571 | } 572 | if (type == end || value == end) return cont(); 573 | if (sep && sep.indexOf(";") > -1) return pass(what) 574 | return cont(expect(end)); 575 | } 576 | return function(type, value) { 577 | if (type == end || value == end) return cont(); 578 | return pass(what, proceed); 579 | }; 580 | } 581 | function contCommasep(what, end, info) { 582 | for (var i = 3; i < arguments.length; i++) 583 | cx.cc.push(arguments[i]); 584 | return cont(pushlex(end, info), commasep(what, end), poplex); 585 | } 586 | function block(type) { 587 | if (type == "}") return cont(); 588 | return pass(statement, block); 589 | } 590 | function maybetype(type, value) { 591 | if (isTS) { 592 | if (type == ":") return cont(typeexpr); 593 | if (value == "?") return cont(maybetype); 594 | } 595 | } 596 | function maybetypeOrIn(type, value) { 597 | if (isTS && (type == ":" || value == "in")) return cont(typeexpr) 598 | } 599 | function mayberettype(type) { 600 | if (isTS && type == ":") { 601 | if (cx.stream.match(/^\s*\w+\s+is\b/, false)) return cont(expression, isKW, typeexpr) 602 | else return cont(typeexpr) 603 | } 604 | } 605 | function isKW(_, value) { 606 | if (value == "is") { 607 | cx.marked = "keyword" 608 | return cont() 609 | } 610 | } 611 | function typeexpr(type, value) { 612 | if (value == "keyof" || value == "typeof" || value == "infer" || value == "readonly") { 613 | cx.marked = "keyword" 614 | return cont(value == "typeof" ? expressionNoComma : typeexpr) 615 | } 616 | if (type == "variable" || value == "void") { 617 | cx.marked = "type" 618 | return cont(afterType) 619 | } 620 | if (value == "|" || value == "&") return cont(typeexpr) 621 | if (type == "string" || type == "number" || type == "atom") return cont(afterType); 622 | if (type == "[") return cont(pushlex("]"), commasep(typeexpr, "]", ","), poplex, afterType) 623 | if (type == "{") return cont(pushlex("}"), typeprops, poplex, afterType) 624 | if (type == "(") return cont(commasep(typearg, ")"), maybeReturnType, afterType) 625 | if (type == "<") return cont(commasep(typeexpr, ">"), typeexpr) 626 | if (type == "quasi") { return pass(quasiType, afterType); } 627 | } 628 | function maybeReturnType(type) { 629 | if (type == "=>") return cont(typeexpr) 630 | } 631 | function typeprops(type) { 632 | if (type.match(/[\}\)\]]/)) return cont() 633 | if (type == "," || type == ";") return cont(typeprops) 634 | return pass(typeprop, typeprops) 635 | } 636 | function typeprop(type, value) { 637 | if (type == "variable" || cx.style == "keyword") { 638 | cx.marked = "property" 639 | return cont(typeprop) 640 | } else if (value == "?" || type == "number" || type == "string") { 641 | return cont(typeprop) 642 | } else if (type == ":") { 643 | return cont(typeexpr) 644 | } else if (type == "[") { 645 | return cont(expect("variable"), maybetypeOrIn, expect("]"), typeprop) 646 | } else if (type == "(") { 647 | return pass(functiondecl, typeprop) 648 | } else if (!type.match(/[;\}\)\],]/)) { 649 | return cont() 650 | } 651 | } 652 | function quasiType(type, value) { 653 | if (type != "quasi") return pass(); 654 | if (value.slice(value.length - 2) != "${") return cont(quasiType); 655 | return cont(typeexpr, continueQuasiType); 656 | } 657 | function continueQuasiType(type) { 658 | if (type == "}") { 659 | cx.marked = "string-2"; 660 | cx.state.tokenize = tokenQuasi; 661 | return cont(quasiType); 662 | } 663 | } 664 | function typearg(type, value) { 665 | if (type == "variable" && cx.stream.match(/^\s*[?:]/, false) || value == "?") return cont(typearg) 666 | if (type == ":") return cont(typeexpr) 667 | if (type == "spread") return cont(typearg) 668 | return pass(typeexpr) 669 | } 670 | function afterType(type, value) { 671 | if (value == "<") return cont(pushlex(">"), commasep(typeexpr, ">"), poplex, afterType) 672 | if (value == "|" || type == "." || value == "&") return cont(typeexpr) 673 | if (type == "[") return cont(typeexpr, expect("]"), afterType) 674 | if (value == "extends" || value == "implements") { cx.marked = "keyword"; return cont(typeexpr) } 675 | if (value == "?") return cont(typeexpr, expect(":"), typeexpr) 676 | } 677 | function maybeTypeArgs(_, value) { 678 | if (value == "<") return cont(pushlex(">"), commasep(typeexpr, ">"), poplex, afterType) 679 | } 680 | function typeparam() { 681 | return pass(typeexpr, maybeTypeDefault) 682 | } 683 | function maybeTypeDefault(_, value) { 684 | if (value == "=") return cont(typeexpr) 685 | } 686 | function vardef(_, value) { 687 | if (value == "enum") {cx.marked = "keyword"; return cont(enumdef)} 688 | return pass(pattern, maybetype, maybeAssign, vardefCont); 689 | } 690 | function pattern(type, value) { 691 | if (isTS && isModifier(value)) { cx.marked = "keyword"; return cont(pattern) } 692 | if (type == "variable") { register(value); return cont(); } 693 | if (type == "spread") return cont(pattern); 694 | if (type == "[") return contCommasep(eltpattern, "]"); 695 | if (type == "{") return contCommasep(proppattern, "}"); 696 | } 697 | function proppattern(type, value) { 698 | if (type == "variable" && !cx.stream.match(/^\s*:/, false)) { 699 | register(value); 700 | return cont(maybeAssign); 701 | } 702 | if (type == "variable") cx.marked = "property"; 703 | if (type == "spread") return cont(pattern); 704 | if (type == "}") return pass(); 705 | if (type == "[") return cont(expression, expect(']'), expect(':'), proppattern); 706 | return cont(expect(":"), pattern, maybeAssign); 707 | } 708 | function eltpattern() { 709 | return pass(pattern, maybeAssign) 710 | } 711 | function maybeAssign(_type, value) { 712 | if (value == "=") return cont(expressionNoComma); 713 | } 714 | function vardefCont(type) { 715 | if (type == ",") return cont(vardef); 716 | } 717 | function maybeelse(type, value) { 718 | if (type == "keyword b" && value == "else") return cont(pushlex("form", "else"), statement, poplex); 719 | } 720 | function forspec(type, value) { 721 | if (value == "await") return cont(forspec); 722 | if (type == "(") return cont(pushlex(")"), forspec1, poplex); 723 | } 724 | function forspec1(type) { 725 | if (type == "var") return cont(vardef, forspec2); 726 | if (type == "variable") return cont(forspec2); 727 | return pass(forspec2) 728 | } 729 | function forspec2(type, value) { 730 | if (type == ")") return cont() 731 | if (type == ";") return cont(forspec2) 732 | if (value == "in" || value == "of") { cx.marked = "keyword"; return cont(expression, forspec2) } 733 | return pass(expression, forspec2) 734 | } 735 | function functiondef(type, value) { 736 | if (value == "*") {cx.marked = "keyword"; return cont(functiondef);} 737 | if (type == "variable") {register(value); return cont(functiondef);} 738 | if (type == "(") return cont(pushcontext, pushlex(")"), commasep(funarg, ")"), poplex, mayberettype, statement, popcontext); 739 | if (isTS && value == "<") return cont(pushlex(">"), commasep(typeparam, ">"), poplex, functiondef) 740 | } 741 | function functiondecl(type, value) { 742 | if (value == "*") {cx.marked = "keyword"; return cont(functiondecl);} 743 | if (type == "variable") {register(value); return cont(functiondecl);} 744 | if (type == "(") return cont(pushcontext, pushlex(")"), commasep(funarg, ")"), poplex, mayberettype, popcontext); 745 | if (isTS && value == "<") return cont(pushlex(">"), commasep(typeparam, ">"), poplex, functiondecl) 746 | } 747 | function typename(type, value) { 748 | if (type == "keyword" || type == "variable") { 749 | cx.marked = "type" 750 | return cont(typename) 751 | } else if (value == "<") { 752 | return cont(pushlex(">"), commasep(typeparam, ">"), poplex) 753 | } 754 | } 755 | function funarg(type, value) { 756 | if (value == "@") cont(expression, funarg) 757 | if (type == "spread") return cont(funarg); 758 | if (isTS && isModifier(value)) { cx.marked = "keyword"; return cont(funarg); } 759 | if (isTS && type == "this") return cont(maybetype, maybeAssign) 760 | return pass(pattern, maybetype, maybeAssign); 761 | } 762 | function classExpression(type, value) { 763 | // Class expressions may have an optional name. 764 | if (type == "variable") return className(type, value); 765 | return classNameAfter(type, value); 766 | } 767 | function className(type, value) { 768 | if (type == "variable") {register(value); return cont(classNameAfter);} 769 | } 770 | function classNameAfter(type, value) { 771 | if (value == "<") return cont(pushlex(">"), commasep(typeparam, ">"), poplex, classNameAfter) 772 | if (value == "extends" || value == "implements" || (isTS && type == ",")) { 773 | if (value == "implements") cx.marked = "keyword"; 774 | return cont(isTS ? typeexpr : expression, classNameAfter); 775 | } 776 | if (type == "{") return cont(pushlex("}"), classBody, poplex); 777 | } 778 | function classBody(type, value) { 779 | if (type == "async" || 780 | (type == "variable" && 781 | (value == "static" || value == "get" || value == "set" || (isTS && isModifier(value))) && 782 | cx.stream.match(/^\s+[\w$\xa1-\uffff]/, false))) { 783 | cx.marked = "keyword"; 784 | return cont(classBody); 785 | } 786 | if (type == "variable" || cx.style == "keyword") { 787 | cx.marked = "property"; 788 | return cont(classfield, classBody); 789 | } 790 | if (type == "number" || type == "string") return cont(classfield, classBody); 791 | if (type == "[") 792 | return cont(expression, maybetype, expect("]"), classfield, classBody) 793 | if (value == "*") { 794 | cx.marked = "keyword"; 795 | return cont(classBody); 796 | } 797 | if (isTS && type == "(") return pass(functiondecl, classBody) 798 | if (type == ";" || type == ",") return cont(classBody); 799 | if (type == "}") return cont(); 800 | if (value == "@") return cont(expression, classBody) 801 | } 802 | function classfield(type, value) { 803 | if (value == "!") return cont(classfield) 804 | if (value == "?") return cont(classfield) 805 | if (type == ":") return cont(typeexpr, maybeAssign) 806 | if (value == "=") return cont(expressionNoComma) 807 | var context = cx.state.lexical.prev, isInterface = context && context.info == "interface" 808 | return pass(isInterface ? functiondecl : functiondef) 809 | } 810 | function afterExport(type, value) { 811 | if (value == "*") { cx.marked = "keyword"; return cont(maybeFrom, expect(";")); } 812 | if (value == "default") { cx.marked = "keyword"; return cont(expression, expect(";")); } 813 | if (type == "{") return cont(commasep(exportField, "}"), maybeFrom, expect(";")); 814 | return pass(statement); 815 | } 816 | function exportField(type, value) { 817 | if (value == "as") { cx.marked = "keyword"; return cont(expect("variable")); } 818 | if (type == "variable") return pass(expressionNoComma, exportField); 819 | } 820 | function afterImport(type) { 821 | if (type == "string") return cont(); 822 | if (type == "(") return pass(expression); 823 | if (type == ".") return pass(maybeoperatorComma); 824 | return pass(importSpec, maybeMoreImports, maybeFrom); 825 | } 826 | function importSpec(type, value) { 827 | if (type == "{") return contCommasep(importSpec, "}"); 828 | if (type == "variable") register(value); 829 | if (value == "*") cx.marked = "keyword"; 830 | return cont(maybeAs); 831 | } 832 | function maybeMoreImports(type) { 833 | if (type == ",") return cont(importSpec, maybeMoreImports) 834 | } 835 | function maybeAs(_type, value) { 836 | if (value == "as") { cx.marked = "keyword"; return cont(importSpec); } 837 | } 838 | function maybeFrom(_type, value) { 839 | if (value == "from") { cx.marked = "keyword"; return cont(expression); } 840 | } 841 | function arrayLiteral(type) { 842 | if (type == "]") return cont(); 843 | return pass(commasep(expressionNoComma, "]")); 844 | } 845 | function enumdef() { 846 | return pass(pushlex("form"), pattern, expect("{"), pushlex("}"), commasep(enummember, "}"), poplex, poplex) 847 | } 848 | function enummember() { 849 | return pass(pattern, maybeAssign); 850 | } 851 | 852 | function isContinuedStatement(state, textAfter) { 853 | return state.lastType == "operator" || state.lastType == "," || 854 | isOperatorChar.test(textAfter.charAt(0)) || 855 | /[,.]/.test(textAfter.charAt(0)); 856 | } 857 | 858 | function expressionAllowed(stream, state, backUp) { 859 | return state.tokenize == tokenBase && 860 | /^(?:operator|sof|keyword [bcd]|case|new|export|default|spread|[\[{}\(,;:]|=>)$/.test(state.lastType) || 861 | (state.lastType == "quasi" && /\{\s*$/.test(stream.string.slice(0, stream.pos - (backUp || 0)))) 862 | } 863 | 864 | // Interface 865 | 866 | return { 867 | startState: function(basecolumn) { 868 | var state = { 869 | tokenize: tokenBase, 870 | lastType: "sof", 871 | cc: [], 872 | lexical: new JSLexical((basecolumn || 0) - indentUnit, 0, "block", false), 873 | localVars: parserConfig.localVars, 874 | context: parserConfig.localVars && new Context(null, null, false), 875 | indented: basecolumn || 0 876 | }; 877 | if (parserConfig.globalVars && typeof parserConfig.globalVars == "object") 878 | state.globalVars = parserConfig.globalVars; 879 | return state; 880 | }, 881 | 882 | token: function(stream, state) { 883 | if (stream.sol()) { 884 | if (!state.lexical.hasOwnProperty("align")) 885 | state.lexical.align = false; 886 | state.indented = stream.indentation(); 887 | findFatArrow(stream, state); 888 | } 889 | if (state.tokenize != tokenComment && stream.eatSpace()) return null; 890 | var style = state.tokenize(stream, state); 891 | if (type == "comment") return style; 892 | state.lastType = type == "operator" && (content == "++" || content == "--") ? "incdec" : type; 893 | return parseJS(state, style, type, content, stream); 894 | }, 895 | 896 | indent: function(state, textAfter) { 897 | if (state.tokenize == tokenComment || state.tokenize == tokenQuasi) return CodeMirror.Pass; 898 | if (state.tokenize != tokenBase) return 0; 899 | var firstChar = textAfter && textAfter.charAt(0), lexical = state.lexical, top 900 | // Kludge to prevent 'maybelse' from blocking lexical scope pops 901 | if (!/^\s*else\b/.test(textAfter)) for (var i = state.cc.length - 1; i >= 0; --i) { 902 | var c = state.cc[i]; 903 | if (c == poplex) lexical = lexical.prev; 904 | else if (c != maybeelse && c != popcontext) break; 905 | } 906 | while ((lexical.type == "stat" || lexical.type == "form") && 907 | (firstChar == "}" || ((top = state.cc[state.cc.length - 1]) && 908 | (top == maybeoperatorComma || top == maybeoperatorNoComma) && 909 | !/^[,\.=+\-*:?[\(]/.test(textAfter)))) 910 | lexical = lexical.prev; 911 | if (statementIndent && lexical.type == ")" && lexical.prev.type == "stat") 912 | lexical = lexical.prev; 913 | var type = lexical.type, closing = firstChar == type; 914 | 915 | if (type == "vardef") return lexical.indented + (state.lastType == "operator" || state.lastType == "," ? lexical.info.length + 1 : 0); 916 | else if (type == "form" && firstChar == "{") return lexical.indented; 917 | else if (type == "form") return lexical.indented + indentUnit; 918 | else if (type == "stat") 919 | return lexical.indented + (isContinuedStatement(state, textAfter) ? statementIndent || indentUnit : 0); 920 | else if (lexical.info == "switch" && !closing && parserConfig.doubleIndentSwitch != false) 921 | return lexical.indented + (/^(?:case|default)\b/.test(textAfter) ? indentUnit : 2 * indentUnit); 922 | else if (lexical.align) return lexical.column + (closing ? 0 : 1); 923 | else return lexical.indented + (closing ? 0 : indentUnit); 924 | }, 925 | 926 | electricInput: /^\s*(?:case .*?:|default:|\{|\})$/, 927 | blockCommentStart: jsonMode ? null : "/*", 928 | blockCommentEnd: jsonMode ? null : "*/", 929 | blockCommentContinue: jsonMode ? null : " * ", 930 | lineComment: jsonMode ? null : "//", 931 | fold: "brace", 932 | closeBrackets: "()[]{}''\"\"``", 933 | 934 | helperType: jsonMode ? "json" : "javascript", 935 | jsonldMode: jsonldMode, 936 | jsonMode: jsonMode, 937 | 938 | expressionAllowed: expressionAllowed, 939 | 940 | skipExpression: function(state) { 941 | parseJS(state, "atom", "atom", "true", new CodeMirror.StringStream("", 2, null)) 942 | } 943 | }; 944 | }); 945 | 946 | CodeMirror.registerHelper("wordChars", "javascript", /[\w$]/); 947 | 948 | CodeMirror.defineMIME("text/javascript", "javascript"); 949 | CodeMirror.defineMIME("text/ecmascript", "javascript"); 950 | CodeMirror.defineMIME("application/javascript", "javascript"); 951 | CodeMirror.defineMIME("application/x-javascript", "javascript"); 952 | CodeMirror.defineMIME("application/ecmascript", "javascript"); 953 | CodeMirror.defineMIME("application/json", { name: "javascript", json: true }); 954 | CodeMirror.defineMIME("application/x-json", { name: "javascript", json: true }); 955 | CodeMirror.defineMIME("application/manifest+json", { name: "javascript", json: true }) 956 | CodeMirror.defineMIME("application/ld+json", { name: "javascript", jsonld: true }); 957 | CodeMirror.defineMIME("text/typescript", { name: "javascript", typescript: true }); 958 | CodeMirror.defineMIME("application/typescript", { name: "javascript", typescript: true }); 959 | 960 | }); 961 | -------------------------------------------------------------------------------- /public/js/codemirror/python.js: -------------------------------------------------------------------------------- 1 | // CodeMirror, copyright (c) by Marijn Haverbeke and others 2 | // Distributed under an MIT license: https://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 | 14 | function wordRegexp(words) { 15 | return new RegExp("^((" + words.join(")|(") + "))\\b"); 16 | } 17 | 18 | var wordOperators = wordRegexp(["and", "or", "not", "is"]); 19 | var commonKeywords = ["as", "assert", "break", "class", "continue", 20 | "def", "del", "elif", "else", "except", "finally", 21 | "for", "from", "global", "if", "import", 22 | "lambda", "pass", "raise", "return", 23 | "try", "while", "with", "yield", "in"]; 24 | var commonBuiltins = ["abs", "all", "any", "bin", "bool", "bytearray", "callable", "chr", 25 | "classmethod", "compile", "complex", "delattr", "dict", "dir", "divmod", 26 | "enumerate", "eval", "filter", "float", "format", "frozenset", 27 | "getattr", "globals", "hasattr", "hash", "help", "hex", "id", 28 | "input", "int", "isinstance", "issubclass", "iter", "len", 29 | "list", "locals", "map", "max", "memoryview", "min", "next", 30 | "object", "oct", "open", "ord", "pow", "property", "range", 31 | "repr", "reversed", "round", "set", "setattr", "slice", 32 | "sorted", "staticmethod", "str", "sum", "super", "tuple", 33 | "type", "vars", "zip", "__import__", "NotImplemented", 34 | "Ellipsis", "__debug__"]; 35 | CodeMirror.registerHelper("hintWords", "python", commonKeywords.concat(commonBuiltins)); 36 | 37 | function top(state) { 38 | return state.scopes[state.scopes.length - 1]; 39 | } 40 | 41 | CodeMirror.defineMode("python", function(conf, parserConf) { 42 | var ERRORCLASS = "error"; 43 | 44 | var delimiters = parserConf.delimiters || parserConf.singleDelimiters || /^[\(\)\[\]\{\}@,:`=;\.\\]/; 45 | // (Backwards-compatibility with old, cumbersome config system) 46 | var operators = [parserConf.singleOperators, parserConf.doubleOperators, parserConf.doubleDelimiters, parserConf.tripleDelimiters, 47 | parserConf.operators || /^([-+*/%\/&|^]=?|[<>=]+|\/\/=?|\*\*=?|!=|[~!@]|\.\.\.)/] 48 | for (var i = 0; i < operators.length; i++) if (!operators[i]) operators.splice(i--, 1) 49 | 50 | var hangingIndent = parserConf.hangingIndent || conf.indentUnit; 51 | 52 | var myKeywords = commonKeywords, myBuiltins = commonBuiltins; 53 | if (parserConf.extra_keywords != undefined) 54 | myKeywords = myKeywords.concat(parserConf.extra_keywords); 55 | 56 | if (parserConf.extra_builtins != undefined) 57 | myBuiltins = myBuiltins.concat(parserConf.extra_builtins); 58 | 59 | var py3 = !(parserConf.version && Number(parserConf.version) < 3) 60 | if (py3) { 61 | // since http://legacy.python.org/dev/peps/pep-0465/ @ is also an operator 62 | var identifiers = parserConf.identifiers|| /^[_A-Za-z\u00A1-\uFFFF][_A-Za-z0-9\u00A1-\uFFFF]*/; 63 | myKeywords = myKeywords.concat(["nonlocal", "False", "True", "None", "async", "await"]); 64 | myBuiltins = myBuiltins.concat(["ascii", "bytes", "exec", "print"]); 65 | var stringPrefixes = new RegExp("^(([rbuf]|(br)|(rb)|(fr)|(rf))?('{3}|\"{3}|['\"]))", "i"); 66 | } else { 67 | var identifiers = parserConf.identifiers|| /^[_A-Za-z][_A-Za-z0-9]*/; 68 | myKeywords = myKeywords.concat(["exec", "print"]); 69 | myBuiltins = myBuiltins.concat(["apply", "basestring", "buffer", "cmp", "coerce", "execfile", 70 | "file", "intern", "long", "raw_input", "reduce", "reload", 71 | "unichr", "unicode", "xrange", "False", "True", "None"]); 72 | var stringPrefixes = new RegExp("^(([rubf]|(ur)|(br))?('{3}|\"{3}|['\"]))", "i"); 73 | } 74 | var keywords = wordRegexp(myKeywords); 75 | var builtins = wordRegexp(myBuiltins); 76 | 77 | // tokenizers 78 | function tokenBase(stream, state) { 79 | var sol = stream.sol() && state.lastToken != "\\" 80 | if (sol) state.indent = stream.indentation() 81 | // Handle scope changes 82 | if (sol && top(state).type == "py") { 83 | var scopeOffset = top(state).offset; 84 | if (stream.eatSpace()) { 85 | var lineOffset = stream.indentation(); 86 | if (lineOffset > scopeOffset) 87 | pushPyScope(state); 88 | else if (lineOffset < scopeOffset && dedent(stream, state) && stream.peek() != "#") 89 | state.errorToken = true; 90 | return null; 91 | } else { 92 | var style = tokenBaseInner(stream, state); 93 | if (scopeOffset > 0 && dedent(stream, state)) 94 | style += " " + ERRORCLASS; 95 | return style; 96 | } 97 | } 98 | return tokenBaseInner(stream, state); 99 | } 100 | 101 | function tokenBaseInner(stream, state, inFormat) { 102 | if (stream.eatSpace()) return null; 103 | 104 | // Handle Comments 105 | if (!inFormat && stream.match(/^#.*/)) return "comment"; 106 | 107 | // Handle Number Literals 108 | if (stream.match(/^[0-9\.]/, false)) { 109 | var floatLiteral = false; 110 | // Floats 111 | if (stream.match(/^[\d_]*\.\d+(e[\+\-]?\d+)?/i)) { floatLiteral = true; } 112 | if (stream.match(/^[\d_]+\.\d*/)) { floatLiteral = true; } 113 | if (stream.match(/^\.\d+/)) { floatLiteral = true; } 114 | if (floatLiteral) { 115 | // Float literals may be "imaginary" 116 | stream.eat(/J/i); 117 | return "number"; 118 | } 119 | // Integers 120 | var intLiteral = false; 121 | // Hex 122 | if (stream.match(/^0x[0-9a-f_]+/i)) intLiteral = true; 123 | // Binary 124 | if (stream.match(/^0b[01_]+/i)) intLiteral = true; 125 | // Octal 126 | if (stream.match(/^0o[0-7_]+/i)) intLiteral = true; 127 | // Decimal 128 | if (stream.match(/^[1-9][\d_]*(e[\+\-]?[\d_]+)?/)) { 129 | // Decimal literals may be "imaginary" 130 | stream.eat(/J/i); 131 | // TODO - Can you have imaginary longs? 132 | intLiteral = true; 133 | } 134 | // Zero by itself with no other piece of number. 135 | if (stream.match(/^0(?![\dx])/i)) intLiteral = true; 136 | if (intLiteral) { 137 | // Integer literals may be "long" 138 | stream.eat(/L/i); 139 | return "number"; 140 | } 141 | } 142 | 143 | // Handle Strings 144 | if (stream.match(stringPrefixes)) { 145 | var isFmtString = stream.current().toLowerCase().indexOf('f') !== -1; 146 | if (!isFmtString) { 147 | state.tokenize = tokenStringFactory(stream.current(), state.tokenize); 148 | return state.tokenize(stream, state); 149 | } else { 150 | state.tokenize = formatStringFactory(stream.current(), state.tokenize); 151 | return state.tokenize(stream, state); 152 | } 153 | } 154 | 155 | for (var i = 0; i < operators.length; i++) 156 | if (stream.match(operators[i])) return "operator" 157 | 158 | if (stream.match(delimiters)) return "punctuation"; 159 | 160 | if (state.lastToken == "." && stream.match(identifiers)) 161 | return "property"; 162 | 163 | if (stream.match(keywords) || stream.match(wordOperators)) 164 | return "keyword"; 165 | 166 | if (stream.match(builtins)) 167 | return "builtin"; 168 | 169 | if (stream.match(/^(self|cls)\b/)) 170 | return "variable-2"; 171 | 172 | if (stream.match(identifiers)) { 173 | if (state.lastToken == "def" || state.lastToken == "class") 174 | return "def"; 175 | return "variable"; 176 | } 177 | 178 | // Handle non-detected items 179 | stream.next(); 180 | return inFormat ? null :ERRORCLASS; 181 | } 182 | 183 | function formatStringFactory(delimiter, tokenOuter) { 184 | while ("rubf".indexOf(delimiter.charAt(0).toLowerCase()) >= 0) 185 | delimiter = delimiter.substr(1); 186 | 187 | var singleline = delimiter.length == 1; 188 | var OUTCLASS = "string"; 189 | 190 | function tokenNestedExpr(depth) { 191 | return function(stream, state) { 192 | var inner = tokenBaseInner(stream, state, true) 193 | if (inner == "punctuation") { 194 | if (stream.current() == "{") { 195 | state.tokenize = tokenNestedExpr(depth + 1) 196 | } else if (stream.current() == "}") { 197 | if (depth > 1) state.tokenize = tokenNestedExpr(depth - 1) 198 | else state.tokenize = tokenString 199 | } 200 | } 201 | return inner 202 | } 203 | } 204 | 205 | function tokenString(stream, state) { 206 | while (!stream.eol()) { 207 | stream.eatWhile(/[^'"\{\}\\]/); 208 | if (stream.eat("\\")) { 209 | stream.next(); 210 | if (singleline && stream.eol()) 211 | return OUTCLASS; 212 | } else if (stream.match(delimiter)) { 213 | state.tokenize = tokenOuter; 214 | return OUTCLASS; 215 | } else if (stream.match('{{')) { 216 | // ignore {{ in f-str 217 | return OUTCLASS; 218 | } else if (stream.match('{', false)) { 219 | // switch to nested mode 220 | state.tokenize = tokenNestedExpr(0) 221 | if (stream.current()) return OUTCLASS; 222 | else return state.tokenize(stream, state) 223 | } else if (stream.match('}}')) { 224 | return OUTCLASS; 225 | } else if (stream.match('}')) { 226 | // single } in f-string is an error 227 | return ERRORCLASS; 228 | } else { 229 | stream.eat(/['"]/); 230 | } 231 | } 232 | if (singleline) { 233 | if (parserConf.singleLineStringErrors) 234 | return ERRORCLASS; 235 | else 236 | state.tokenize = tokenOuter; 237 | } 238 | return OUTCLASS; 239 | } 240 | tokenString.isString = true; 241 | return tokenString; 242 | } 243 | 244 | function tokenStringFactory(delimiter, tokenOuter) { 245 | while ("rubf".indexOf(delimiter.charAt(0).toLowerCase()) >= 0) 246 | delimiter = delimiter.substr(1); 247 | 248 | var singleline = delimiter.length == 1; 249 | var OUTCLASS = "string"; 250 | 251 | function tokenString(stream, state) { 252 | while (!stream.eol()) { 253 | stream.eatWhile(/[^'"\\]/); 254 | if (stream.eat("\\")) { 255 | stream.next(); 256 | if (singleline && stream.eol()) 257 | return OUTCLASS; 258 | } else if (stream.match(delimiter)) { 259 | state.tokenize = tokenOuter; 260 | return OUTCLASS; 261 | } else { 262 | stream.eat(/['"]/); 263 | } 264 | } 265 | if (singleline) { 266 | if (parserConf.singleLineStringErrors) 267 | return ERRORCLASS; 268 | else 269 | state.tokenize = tokenOuter; 270 | } 271 | return OUTCLASS; 272 | } 273 | tokenString.isString = true; 274 | return tokenString; 275 | } 276 | 277 | function pushPyScope(state) { 278 | while (top(state).type != "py") state.scopes.pop() 279 | state.scopes.push({offset: top(state).offset + conf.indentUnit, 280 | type: "py", 281 | align: null}) 282 | } 283 | 284 | function pushBracketScope(stream, state, type) { 285 | var align = stream.match(/^[\s\[\{\(]*(?:#|$)/, false) ? null : stream.column() + 1 286 | state.scopes.push({offset: state.indent + hangingIndent, 287 | type: type, 288 | align: align}) 289 | } 290 | 291 | function dedent(stream, state) { 292 | var indented = stream.indentation(); 293 | while (state.scopes.length > 1 && top(state).offset > indented) { 294 | if (top(state).type != "py") return true; 295 | state.scopes.pop(); 296 | } 297 | return top(state).offset != indented; 298 | } 299 | 300 | function tokenLexer(stream, state) { 301 | if (stream.sol()) { 302 | state.beginningOfLine = true; 303 | state.dedent = false; 304 | } 305 | 306 | var style = state.tokenize(stream, state); 307 | var current = stream.current(); 308 | 309 | // Handle decorators 310 | if (state.beginningOfLine && current == "@") 311 | return stream.match(identifiers, false) ? "meta" : py3 ? "operator" : ERRORCLASS; 312 | 313 | if (/\S/.test(current)) state.beginningOfLine = false; 314 | 315 | if ((style == "variable" || style == "builtin") 316 | && state.lastToken == "meta") 317 | style = "meta"; 318 | 319 | // Handle scope changes. 320 | if (current == "pass" || current == "return") 321 | state.dedent = true; 322 | 323 | if (current == "lambda") state.lambda = true; 324 | if (current == ":" && !state.lambda && top(state).type == "py" && stream.match(/^\s*(?:#|$)/, false)) 325 | pushPyScope(state); 326 | 327 | if (current.length == 1 && !/string|comment/.test(style)) { 328 | var delimiter_index = "[({".indexOf(current); 329 | if (delimiter_index != -1) 330 | pushBracketScope(stream, state, "])}".slice(delimiter_index, delimiter_index+1)); 331 | 332 | delimiter_index = "])}".indexOf(current); 333 | if (delimiter_index != -1) { 334 | if (top(state).type == current) state.indent = state.scopes.pop().offset - hangingIndent 335 | else return ERRORCLASS; 336 | } 337 | } 338 | if (state.dedent && stream.eol() && top(state).type == "py" && state.scopes.length > 1) 339 | state.scopes.pop(); 340 | 341 | return style; 342 | } 343 | 344 | var external = { 345 | startState: function(basecolumn) { 346 | return { 347 | tokenize: tokenBase, 348 | scopes: [{offset: basecolumn || 0, type: "py", align: null}], 349 | indent: basecolumn || 0, 350 | lastToken: null, 351 | lambda: false, 352 | dedent: 0 353 | }; 354 | }, 355 | 356 | token: function(stream, state) { 357 | var addErr = state.errorToken; 358 | if (addErr) state.errorToken = false; 359 | var style = tokenLexer(stream, state); 360 | 361 | if (style && style != "comment") 362 | state.lastToken = (style == "keyword" || style == "punctuation") ? stream.current() : style; 363 | if (style == "punctuation") style = null; 364 | 365 | if (stream.eol() && state.lambda) 366 | state.lambda = false; 367 | return addErr ? style + " " + ERRORCLASS : style; 368 | }, 369 | 370 | indent: function(state, textAfter) { 371 | if (state.tokenize != tokenBase) 372 | return state.tokenize.isString ? CodeMirror.Pass : 0; 373 | 374 | var scope = top(state) 375 | var closing = scope.type == textAfter.charAt(0) || 376 | scope.type == "py" && !state.dedent && /^(else:|elif |except |finally:)/.test(textAfter) 377 | if (scope.align != null) 378 | return scope.align - (closing ? 1 : 0) 379 | else 380 | return scope.offset - (closing ? hangingIndent : 0) 381 | }, 382 | 383 | electricInput: /^\s*([\}\]\)]|else:|elif |except |finally:)$/, 384 | closeBrackets: {triples: "'\""}, 385 | lineComment: "#", 386 | fold: "indent" 387 | }; 388 | return external; 389 | }); 390 | 391 | CodeMirror.defineMIME("text/x-python", "python"); 392 | 393 | var words = function(str) { return str.split(" "); }; 394 | 395 | CodeMirror.defineMIME("text/x-cython", { 396 | name: "python", 397 | extra_keywords: words("by cdef cimport cpdef ctypedef enum except "+ 398 | "extern gil include nogil property public "+ 399 | "readonly struct union DEF IF ELIF ELSE") 400 | }); 401 | 402 | }); 403 | -------------------------------------------------------------------------------- /public/js/codemirror/show-hint.js: -------------------------------------------------------------------------------- 1 | // CodeMirror, copyright (c) by Marijn Haverbeke and others 2 | // Distributed under an MIT license: https://codemirror.net/LICENSE 3 | 4 | // declare global: DOMRect 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 | "use strict"; 15 | 16 | var HINT_ELEMENT_CLASS = "CodeMirror-hint"; 17 | var ACTIVE_HINT_ELEMENT_CLASS = "CodeMirror-hint-active"; 18 | 19 | // This is the old interface, kept around for now to stay 20 | // backwards-compatible. 21 | CodeMirror.showHint = function(cm, getHints, options) { 22 | if (!getHints) return cm.showHint(options); 23 | if (options && options.async) getHints.async = true; 24 | var newOpts = {hint: getHints}; 25 | if (options) for (var prop in options) newOpts[prop] = options[prop]; 26 | return cm.showHint(newOpts); 27 | }; 28 | 29 | CodeMirror.defineExtension("showHint", function(options) { 30 | options = parseOptions(this, this.getCursor("start"), options); 31 | var selections = this.listSelections() 32 | if (selections.length > 1) return; 33 | // By default, don't allow completion when something is selected. 34 | // A hint function can have a `supportsSelection` property to 35 | // indicate that it can handle selections. 36 | if (this.somethingSelected()) { 37 | if (!options.hint.supportsSelection) return; 38 | // Don't try with cross-line selections 39 | for (var i = 0; i < selections.length; i++) 40 | if (selections[i].head.line != selections[i].anchor.line) return; 41 | } 42 | 43 | if (this.state.completionActive) this.state.completionActive.close(); 44 | var completion = this.state.completionActive = new Completion(this, options); 45 | if (!completion.options.hint) return; 46 | 47 | CodeMirror.signal(this, "startCompletion", this); 48 | completion.update(true); 49 | }); 50 | 51 | CodeMirror.defineExtension("closeHint", function() { 52 | if (this.state.completionActive) this.state.completionActive.close() 53 | }) 54 | 55 | function Completion(cm, options) { 56 | this.cm = cm; 57 | this.options = options; 58 | this.widget = null; 59 | this.debounce = 0; 60 | this.tick = 0; 61 | this.startPos = this.cm.getCursor("start"); 62 | this.startLen = this.cm.getLine(this.startPos.line).length - this.cm.getSelection().length; 63 | 64 | if (this.options.updateOnCursorActivity) { 65 | var self = this; 66 | cm.on("cursorActivity", this.activityFunc = function() { self.cursorActivity(); }); 67 | } 68 | } 69 | 70 | var requestAnimationFrame = window.requestAnimationFrame || function(fn) { 71 | return setTimeout(fn, 1000/60); 72 | }; 73 | var cancelAnimationFrame = window.cancelAnimationFrame || clearTimeout; 74 | 75 | Completion.prototype = { 76 | close: function() { 77 | if (!this.active()) return; 78 | this.cm.state.completionActive = null; 79 | this.tick = null; 80 | if (this.options.updateOnCursorActivity) { 81 | this.cm.off("cursorActivity", this.activityFunc); 82 | } 83 | 84 | if (this.widget && this.data) CodeMirror.signal(this.data, "close"); 85 | if (this.widget) this.widget.close(); 86 | CodeMirror.signal(this.cm, "endCompletion", this.cm); 87 | }, 88 | 89 | active: function() { 90 | return this.cm.state.completionActive == this; 91 | }, 92 | 93 | pick: function(data, i) { 94 | var completion = data.list[i], self = this; 95 | this.cm.operation(function() { 96 | if (completion.hint) 97 | completion.hint(self.cm, data, completion); 98 | else 99 | self.cm.replaceRange(getText(completion), completion.from || data.from, 100 | completion.to || data.to, "complete"); 101 | CodeMirror.signal(data, "pick", completion); 102 | self.cm.scrollIntoView(); 103 | }); 104 | if (this.options.closeOnPick) { 105 | this.close(); 106 | } 107 | }, 108 | 109 | cursorActivity: function() { 110 | if (this.debounce) { 111 | cancelAnimationFrame(this.debounce); 112 | this.debounce = 0; 113 | } 114 | 115 | var identStart = this.startPos; 116 | if(this.data) { 117 | identStart = this.data.from; 118 | } 119 | 120 | var pos = this.cm.getCursor(), line = this.cm.getLine(pos.line); 121 | if (pos.line != this.startPos.line || line.length - pos.ch != this.startLen - this.startPos.ch || 122 | pos.ch < identStart.ch || this.cm.somethingSelected() || 123 | (!pos.ch || this.options.closeCharacters.test(line.charAt(pos.ch - 1)))) { 124 | this.close(); 125 | } else { 126 | var self = this; 127 | this.debounce = requestAnimationFrame(function() {self.update();}); 128 | if (this.widget) this.widget.disable(); 129 | } 130 | }, 131 | 132 | update: function(first) { 133 | if (this.tick == null) return 134 | var self = this, myTick = ++this.tick 135 | fetchHints(this.options.hint, this.cm, this.options, function(data) { 136 | if (self.tick == myTick) self.finishUpdate(data, first) 137 | }) 138 | }, 139 | 140 | finishUpdate: function(data, first) { 141 | if (this.data) CodeMirror.signal(this.data, "update"); 142 | 143 | var picked = (this.widget && this.widget.picked) || (first && this.options.completeSingle); 144 | if (this.widget) this.widget.close(); 145 | 146 | this.data = data; 147 | 148 | if (data && data.list.length) { 149 | if (picked && data.list.length == 1) { 150 | this.pick(data, 0); 151 | } else { 152 | this.widget = new Widget(this, data); 153 | CodeMirror.signal(data, "shown"); 154 | } 155 | } 156 | } 157 | }; 158 | 159 | function parseOptions(cm, pos, options) { 160 | var editor = cm.options.hintOptions; 161 | var out = {}; 162 | for (var prop in defaultOptions) out[prop] = defaultOptions[prop]; 163 | if (editor) for (var prop in editor) 164 | if (editor[prop] !== undefined) out[prop] = editor[prop]; 165 | if (options) for (var prop in options) 166 | if (options[prop] !== undefined) out[prop] = options[prop]; 167 | if (out.hint.resolve) out.hint = out.hint.resolve(cm, pos) 168 | return out; 169 | } 170 | 171 | function getText(completion) { 172 | if (typeof completion == "string") return completion; 173 | else return completion.text; 174 | } 175 | 176 | function buildKeyMap(completion, handle) { 177 | var baseMap = { 178 | Up: function() {handle.moveFocus(-1);}, 179 | Down: function() {handle.moveFocus(1);}, 180 | PageUp: function() {handle.moveFocus(-handle.menuSize() + 1, true);}, 181 | PageDown: function() {handle.moveFocus(handle.menuSize() - 1, true);}, 182 | Home: function() {handle.setFocus(0);}, 183 | End: function() {handle.setFocus(handle.length - 1);}, 184 | Enter: handle.pick, 185 | Tab: handle.pick, 186 | Esc: handle.close 187 | }; 188 | 189 | var mac = /Mac/.test(navigator.platform); 190 | 191 | if (mac) { 192 | baseMap["Ctrl-P"] = function() {handle.moveFocus(-1);}; 193 | baseMap["Ctrl-N"] = function() {handle.moveFocus(1);}; 194 | } 195 | 196 | var custom = completion.options.customKeys; 197 | var ourMap = custom ? {} : baseMap; 198 | function addBinding(key, val) { 199 | var bound; 200 | if (typeof val != "string") 201 | bound = function(cm) { return val(cm, handle); }; 202 | // This mechanism is deprecated 203 | else if (baseMap.hasOwnProperty(val)) 204 | bound = baseMap[val]; 205 | else 206 | bound = val; 207 | ourMap[key] = bound; 208 | } 209 | if (custom) 210 | for (var key in custom) if (custom.hasOwnProperty(key)) 211 | addBinding(key, custom[key]); 212 | var extra = completion.options.extraKeys; 213 | if (extra) 214 | for (var key in extra) if (extra.hasOwnProperty(key)) 215 | addBinding(key, extra[key]); 216 | return ourMap; 217 | } 218 | 219 | function getHintElement(hintsElement, el) { 220 | while (el && el != hintsElement) { 221 | if (el.nodeName.toUpperCase() === "LI" && el.parentNode == hintsElement) return el; 222 | el = el.parentNode; 223 | } 224 | } 225 | 226 | function Widget(completion, data) { 227 | this.id = "cm-complete-" + Math.floor(Math.random(1e6)) 228 | this.completion = completion; 229 | this.data = data; 230 | this.picked = false; 231 | var widget = this, cm = completion.cm; 232 | var ownerDocument = cm.getInputField().ownerDocument; 233 | var parentWindow = ownerDocument.defaultView || ownerDocument.parentWindow; 234 | 235 | var hints = this.hints = ownerDocument.createElement("ul"); 236 | hints.setAttribute("role", "listbox") 237 | hints.setAttribute("aria-expanded", "true") 238 | hints.id = this.id 239 | var theme = completion.cm.options.theme; 240 | hints.className = "CodeMirror-hints " + theme; 241 | this.selectedHint = data.selectedHint || 0; 242 | 243 | var completions = data.list; 244 | for (var i = 0; i < completions.length; ++i) { 245 | var elt = hints.appendChild(ownerDocument.createElement("li")), cur = completions[i]; 246 | var className = HINT_ELEMENT_CLASS + (i != this.selectedHint ? "" : " " + ACTIVE_HINT_ELEMENT_CLASS); 247 | if (cur.className != null) className = cur.className + " " + className; 248 | elt.className = className; 249 | if (i == this.selectedHint) elt.setAttribute("aria-selected", "true") 250 | elt.id = this.id + "-" + i 251 | elt.setAttribute("role", "option") 252 | if (cur.render) cur.render(elt, data, cur); 253 | else elt.appendChild(ownerDocument.createTextNode(cur.displayText || getText(cur))); 254 | elt.hintId = i; 255 | } 256 | 257 | var container = completion.options.container || ownerDocument.body; 258 | var pos = cm.cursorCoords(completion.options.alignWithWord ? data.from : null); 259 | var left = pos.left, top = pos.bottom, below = true; 260 | var offsetLeft = 0, offsetTop = 0; 261 | if (container !== ownerDocument.body) { 262 | // We offset the cursor position because left and top are relative to the offsetParent's top left corner. 263 | var isContainerPositioned = ['absolute', 'relative', 'fixed'].indexOf(parentWindow.getComputedStyle(container).position) !== -1; 264 | var offsetParent = isContainerPositioned ? container : container.offsetParent; 265 | var offsetParentPosition = offsetParent.getBoundingClientRect(); 266 | var bodyPosition = ownerDocument.body.getBoundingClientRect(); 267 | offsetLeft = (offsetParentPosition.left - bodyPosition.left - offsetParent.scrollLeft); 268 | offsetTop = (offsetParentPosition.top - bodyPosition.top - offsetParent.scrollTop); 269 | } 270 | hints.style.left = (left - offsetLeft) + "px"; 271 | hints.style.top = (top - offsetTop) + "px"; 272 | 273 | // If we're at the edge of the screen, then we want the menu to appear on the left of the cursor. 274 | var winW = parentWindow.innerWidth || Math.max(ownerDocument.body.offsetWidth, ownerDocument.documentElement.offsetWidth); 275 | var winH = parentWindow.innerHeight || Math.max(ownerDocument.body.offsetHeight, ownerDocument.documentElement.offsetHeight); 276 | container.appendChild(hints); 277 | cm.getInputField().setAttribute("aria-autocomplete", "list") 278 | cm.getInputField().setAttribute("aria-owns", this.id) 279 | cm.getInputField().setAttribute("aria-activedescendant", this.id + "-" + this.selectedHint) 280 | 281 | var box = completion.options.moveOnOverlap ? hints.getBoundingClientRect() : new DOMRect(); 282 | var scrolls = completion.options.paddingForScrollbar ? hints.scrollHeight > hints.clientHeight + 1 : false; 283 | 284 | // Compute in the timeout to avoid reflow on init 285 | var startScroll; 286 | setTimeout(function() { startScroll = cm.getScrollInfo(); }); 287 | 288 | var overlapY = box.bottom - winH; 289 | if (overlapY > 0) { 290 | var height = box.bottom - box.top, curTop = pos.top - (pos.bottom - box.top); 291 | if (curTop - height > 0) { // Fits above cursor 292 | hints.style.top = (top = pos.top - height - offsetTop) + "px"; 293 | below = false; 294 | } else if (height > winH) { 295 | hints.style.height = (winH - 5) + "px"; 296 | hints.style.top = (top = pos.bottom - box.top - offsetTop) + "px"; 297 | var cursor = cm.getCursor(); 298 | if (data.from.ch != cursor.ch) { 299 | pos = cm.cursorCoords(cursor); 300 | hints.style.left = (left = pos.left - offsetLeft) + "px"; 301 | box = hints.getBoundingClientRect(); 302 | } 303 | } 304 | } 305 | var overlapX = box.right - winW; 306 | if (scrolls) overlapX += cm.display.nativeBarWidth; 307 | if (overlapX > 0) { 308 | if (box.right - box.left > winW) { 309 | hints.style.width = (winW - 5) + "px"; 310 | overlapX -= (box.right - box.left) - winW; 311 | } 312 | hints.style.left = (left = pos.left - overlapX - offsetLeft) + "px"; 313 | } 314 | if (scrolls) for (var node = hints.firstChild; node; node = node.nextSibling) 315 | node.style.paddingRight = cm.display.nativeBarWidth + "px" 316 | 317 | cm.addKeyMap(this.keyMap = buildKeyMap(completion, { 318 | moveFocus: function(n, avoidWrap) { widget.changeActive(widget.selectedHint + n, avoidWrap); }, 319 | setFocus: function(n) { widget.changeActive(n); }, 320 | menuSize: function() { return widget.screenAmount(); }, 321 | length: completions.length, 322 | close: function() { completion.close(); }, 323 | pick: function() { widget.pick(); }, 324 | data: data 325 | })); 326 | 327 | if (completion.options.closeOnUnfocus) { 328 | var closingOnBlur; 329 | cm.on("blur", this.onBlur = function() { closingOnBlur = setTimeout(function() { completion.close(); }, 100); }); 330 | cm.on("focus", this.onFocus = function() { clearTimeout(closingOnBlur); }); 331 | } 332 | 333 | cm.on("scroll", this.onScroll = function() { 334 | var curScroll = cm.getScrollInfo(), editor = cm.getWrapperElement().getBoundingClientRect(); 335 | if (!startScroll) startScroll = cm.getScrollInfo(); 336 | var newTop = top + startScroll.top - curScroll.top; 337 | var point = newTop - (parentWindow.pageYOffset || (ownerDocument.documentElement || ownerDocument.body).scrollTop); 338 | if (!below) point += hints.offsetHeight; 339 | if (point <= editor.top || point >= editor.bottom) return completion.close(); 340 | hints.style.top = newTop + "px"; 341 | hints.style.left = (left + startScroll.left - curScroll.left) + "px"; 342 | }); 343 | 344 | CodeMirror.on(hints, "dblclick", function(e) { 345 | var t = getHintElement(hints, e.target || e.srcElement); 346 | if (t && t.hintId != null) {widget.changeActive(t.hintId); widget.pick();} 347 | }); 348 | 349 | CodeMirror.on(hints, "click", function(e) { 350 | var t = getHintElement(hints, e.target || e.srcElement); 351 | if (t && t.hintId != null) { 352 | widget.changeActive(t.hintId); 353 | if (completion.options.completeOnSingleClick) widget.pick(); 354 | } 355 | }); 356 | 357 | CodeMirror.on(hints, "mousedown", function() { 358 | setTimeout(function(){cm.focus();}, 20); 359 | }); 360 | 361 | // The first hint doesn't need to be scrolled to on init 362 | var selectedHintRange = this.getSelectedHintRange(); 363 | if (selectedHintRange.from !== 0 || selectedHintRange.to !== 0) { 364 | this.scrollToActive(); 365 | } 366 | 367 | CodeMirror.signal(data, "select", completions[this.selectedHint], hints.childNodes[this.selectedHint]); 368 | return true; 369 | } 370 | 371 | Widget.prototype = { 372 | close: function() { 373 | if (this.completion.widget != this) return; 374 | this.completion.widget = null; 375 | if (this.hints.parentNode) this.hints.parentNode.removeChild(this.hints); 376 | this.completion.cm.removeKeyMap(this.keyMap); 377 | var input = this.completion.cm.getInputField() 378 | input.removeAttribute("aria-activedescendant") 379 | input.removeAttribute("aria-owns") 380 | 381 | var cm = this.completion.cm; 382 | if (this.completion.options.closeOnUnfocus) { 383 | cm.off("blur", this.onBlur); 384 | cm.off("focus", this.onFocus); 385 | } 386 | cm.off("scroll", this.onScroll); 387 | }, 388 | 389 | disable: function() { 390 | this.completion.cm.removeKeyMap(this.keyMap); 391 | var widget = this; 392 | this.keyMap = {Enter: function() { widget.picked = true; }}; 393 | this.completion.cm.addKeyMap(this.keyMap); 394 | }, 395 | 396 | pick: function() { 397 | this.completion.pick(this.data, this.selectedHint); 398 | }, 399 | 400 | changeActive: function(i, avoidWrap) { 401 | if (i >= this.data.list.length) 402 | i = avoidWrap ? this.data.list.length - 1 : 0; 403 | else if (i < 0) 404 | i = avoidWrap ? 0 : this.data.list.length - 1; 405 | if (this.selectedHint == i) return; 406 | var node = this.hints.childNodes[this.selectedHint]; 407 | if (node) { 408 | node.className = node.className.replace(" " + ACTIVE_HINT_ELEMENT_CLASS, ""); 409 | node.removeAttribute("aria-selected") 410 | } 411 | node = this.hints.childNodes[this.selectedHint = i]; 412 | node.className += " " + ACTIVE_HINT_ELEMENT_CLASS; 413 | node.setAttribute("aria-selected", "true") 414 | this.completion.cm.getInputField().setAttribute("aria-activedescendant", node.id) 415 | this.scrollToActive() 416 | CodeMirror.signal(this.data, "select", this.data.list[this.selectedHint], node); 417 | }, 418 | 419 | scrollToActive: function() { 420 | var selectedHintRange = this.getSelectedHintRange(); 421 | var node1 = this.hints.childNodes[selectedHintRange.from]; 422 | var node2 = this.hints.childNodes[selectedHintRange.to]; 423 | var firstNode = this.hints.firstChild; 424 | if (node1.offsetTop < this.hints.scrollTop) 425 | this.hints.scrollTop = node1.offsetTop - firstNode.offsetTop; 426 | else if (node2.offsetTop + node2.offsetHeight > this.hints.scrollTop + this.hints.clientHeight) 427 | this.hints.scrollTop = node2.offsetTop + node2.offsetHeight - this.hints.clientHeight + firstNode.offsetTop; 428 | }, 429 | 430 | screenAmount: function() { 431 | return Math.floor(this.hints.clientHeight / this.hints.firstChild.offsetHeight) || 1; 432 | }, 433 | 434 | getSelectedHintRange: function() { 435 | var margin = this.completion.options.scrollMargin || 0; 436 | return { 437 | from: Math.max(0, this.selectedHint - margin), 438 | to: Math.min(this.data.list.length - 1, this.selectedHint + margin), 439 | }; 440 | } 441 | }; 442 | 443 | function applicableHelpers(cm, helpers) { 444 | if (!cm.somethingSelected()) return helpers 445 | var result = [] 446 | for (var i = 0; i < helpers.length; i++) 447 | if (helpers[i].supportsSelection) result.push(helpers[i]) 448 | return result 449 | } 450 | 451 | function fetchHints(hint, cm, options, callback) { 452 | if (hint.async) { 453 | hint(cm, callback, options) 454 | } else { 455 | var result = hint(cm, options) 456 | if (result && result.then) result.then(callback) 457 | else callback(result) 458 | } 459 | } 460 | 461 | function resolveAutoHints(cm, pos) { 462 | var helpers = cm.getHelpers(pos, "hint"), words 463 | if (helpers.length) { 464 | var resolved = function(cm, callback, options) { 465 | var app = applicableHelpers(cm, helpers); 466 | function run(i) { 467 | if (i == app.length) return callback(null) 468 | fetchHints(app[i], cm, options, function(result) { 469 | if (result && result.list.length > 0) callback(result) 470 | else run(i + 1) 471 | }) 472 | } 473 | run(0) 474 | } 475 | resolved.async = true 476 | resolved.supportsSelection = true 477 | return resolved 478 | } else if (words = cm.getHelper(cm.getCursor(), "hintWords")) { 479 | return function(cm) { return CodeMirror.hint.fromList(cm, {words: words}) } 480 | } else if (CodeMirror.hint.anyword) { 481 | return function(cm, options) { return CodeMirror.hint.anyword(cm, options) } 482 | } else { 483 | return function() {} 484 | } 485 | } 486 | 487 | CodeMirror.registerHelper("hint", "auto", { 488 | resolve: resolveAutoHints 489 | }); 490 | 491 | CodeMirror.registerHelper("hint", "fromList", function(cm, options) { 492 | var cur = cm.getCursor(), token = cm.getTokenAt(cur) 493 | var term, from = CodeMirror.Pos(cur.line, token.start), to = cur 494 | if (token.start < cur.ch && /\w/.test(token.string.charAt(cur.ch - token.start - 1))) { 495 | term = token.string.substr(0, cur.ch - token.start) 496 | } else { 497 | term = "" 498 | from = cur 499 | } 500 | var found = []; 501 | for (var i = 0; i < options.words.length; i++) { 502 | var word = options.words[i]; 503 | if (word.slice(0, term.length) == term) 504 | found.push(word); 505 | } 506 | 507 | if (found.length) return {list: found, from: from, to: to}; 508 | }); 509 | 510 | CodeMirror.commands.autocomplete = CodeMirror.showHint; 511 | 512 | var defaultOptions = { 513 | hint: CodeMirror.hint.auto, 514 | completeSingle: true, 515 | alignWithWord: true, 516 | closeCharacters: /[\s()\[\]{};:>,]/, 517 | closeOnPick: true, 518 | closeOnUnfocus: true, 519 | updateOnCursorActivity: true, 520 | completeOnSingleClick: true, 521 | container: null, 522 | customKeys: null, 523 | extraKeys: null, 524 | paddingForScrollbar: true, 525 | moveOnOverlap: true, 526 | }; 527 | 528 | CodeMirror.defineOption("hintOptions", null); 529 | }); 530 | -------------------------------------------------------------------------------- /public/js/home.js: -------------------------------------------------------------------------------- 1 | let startBtn = document.getElementById("start-btn"); 2 | 3 | startBtn.addEventListener("click", async () => { 4 | let room = uuidv4(); 5 | let response = await fetch(`/getToken/${room}`); 6 | let data = await response.json(); 7 | 8 | let UID = data.uid; 9 | let token = data.token; 10 | 11 | sessionStorage.setItem("UID", UID); 12 | sessionStorage.setItem("token", token); 13 | 14 | window.open(`/${room}`, "_self"); 15 | }) 16 | -------------------------------------------------------------------------------- /public/js/liveBoard.js: -------------------------------------------------------------------------------- 1 | const canvasContainer = document.querySelector("#canvasContainer") 2 | let canvasBounds = canvasContainer.getBoundingClientRect() 3 | 4 | const drawingCursor = document.querySelector('#drawing-cursor') 5 | let cursorBounds = drawingCursor.getBoundingClientRect() 6 | 7 | let paintObject = { path: [] } 8 | 9 | let undoStack = [] 10 | let redoStack = [] 11 | 12 | let color = '#3be8b0' 13 | let strokeWidth = 5 14 | let backgroundColor = '#282a36' 15 | 16 | let ctx 17 | 18 | let isDrawing = false 19 | 20 | const canvasWidth = 1000 21 | const canvasHeight = 800 22 | 23 | const resizeObserver = new ResizeObserver(() => { 24 | canvasBounds = canvasContainer.getBoundingClientRect() 25 | }) 26 | 27 | resizeObserver.observe(canvasContainer) 28 | 29 | function setup() { 30 | let canvas = createCanvas(canvasWidth, canvasHeight) 31 | canvas.parent('canvasContainer') 32 | 33 | background(backgroundColor) 34 | strokeJoin(ROUND); 35 | 36 | ctx = document.getElementById("defaultCanvas0") 37 | 38 | saveState(ctx.toDataURL()) 39 | } 40 | 41 | function mouseDragged() { 42 | if (!isDrawing) 43 | return 44 | 45 | stroke(color) 46 | strokeWeight(strokeWidth) 47 | 48 | line(mouseX, mouseY, pmouseX, pmouseY) 49 | 50 | paintObject.path.push([mouseX, mouseY]) 51 | 52 | // if slider is open close it 53 | sliderContainer.style.display = "" 54 | strokeSizeSelecterOpen = false 55 | } 56 | 57 | function mouseReleased() { 58 | if (!isDrawing) 59 | return 60 | 61 | paintObject.color = color 62 | paintObject.strokeWidth = strokeWidth 63 | 64 | socket.emit("send-paint-path", paintObject) 65 | 66 | // clear the paint object 67 | paintObject.path = [] 68 | 69 | saveState(ctx.toDataURL()) 70 | } 71 | 72 | function saveState(state) { 73 | if (undoStack.length == 10) 74 | undoStack.shift() 75 | 76 | undoStack.push(state) 77 | } 78 | 79 | 80 | function paintPath(paintObject) { 81 | const path = paintObject.path 82 | 83 | stroke(paintObject.color) 84 | strokeWeight(paintObject.strokeWidth) 85 | 86 | for (let i = 0; i < path.length - 1; i++) { 87 | line(path[i][0], path[i][1], path[i + 1][0], path[i + 1][1]) 88 | } 89 | 90 | // after painting save to undo stack 91 | saveState(ctx.toDataURL()) 92 | } 93 | 94 | // socket evnets 95 | socket.on("send-state", user => { 96 | socket.emit("send-canvas-state", user, ctx.toDataURL(), undoStack, redoStack) 97 | }) 98 | 99 | socket.on("get-canvas-state", (data, undoData, redoData) => { 100 | loadImage(data, img => { 101 | image(img, 0, 0, canvasWidth, canvasHeight); 102 | }); 103 | 104 | undoStack = [...undoData] 105 | redoStack = [...redoData] 106 | }) 107 | 108 | socket.on("paint", paintObject => { 109 | paintPath(paintObject) 110 | }) 111 | 112 | socket.on('clear-canvas', () => { 113 | saveState(ctx.toDataURL()) 114 | background(backgroundColor) 115 | }) 116 | 117 | 118 | // settings 119 | const colorPicker = document.querySelector("#stroke-color") 120 | const colorBtn = document.querySelector('#color-picker') 121 | const colorCell = document.querySelector('#color1') 122 | const brushSizeBtn = document.querySelector('#brush-size') 123 | const eraser = document.querySelector('#eraser') 124 | const strokeRange = document.querySelector("#stroke-Range") 125 | const sliderContainer = document.querySelector(".slidecontainer") 126 | const clearBtn = document.querySelector("#clear-canvas") 127 | const undoBtn = document.querySelector("#undoBtn") 128 | const redoBtn = document.querySelector("#redoBtn") 129 | 130 | colorBtn.addEventListener("click", () => { 131 | colorPicker.click() 132 | }) 133 | 134 | colorCell.addEventListener("click", () => { 135 | color = colorPicker.value 136 | 137 | drawingCursor.style.backgroundColor = color 138 | }) 139 | 140 | colorPicker.addEventListener('change', e => { 141 | color = e.target.value 142 | colorCell.style.backgroundColor = color 143 | 144 | drawingCursor.style.backgroundColor = color 145 | }) 146 | 147 | colorPicker.addEventListener('click', e => { 148 | color = e.target.value 149 | 150 | drawingCursor.style.backgroundColor = color 151 | }) 152 | 153 | 154 | let strokeSizeSelecterOpen = false 155 | brushSizeBtn.addEventListener("click", () => { 156 | if (!strokeSizeSelecterOpen) 157 | sliderContainer.style.display = "inline-block" 158 | else 159 | sliderContainer.style.display = "" 160 | 161 | strokeSizeSelecterOpen = !strokeSizeSelecterOpen 162 | }) 163 | 164 | eraser.addEventListener('click', () => { 165 | color = backgroundColor 166 | drawingCursor.style.backgroundColor = 'white' 167 | }) 168 | 169 | strokeRange.addEventListener("change", e => { 170 | strokeWidth = e.target.value 171 | 172 | drawingCursor.style.width = e.target.value + "px" 173 | drawingCursor.style.height = e.target.value + "px" 174 | 175 | cursorBounds = drawingCursor.getBoundingClientRect() 176 | }) 177 | 178 | clearBtn.addEventListener("click", () => { 179 | saveState(ctx.toDataURL()) 180 | background(backgroundColor) 181 | socket.emit("trigger-clear-canvas", ROOM_ID) 182 | }) 183 | 184 | 185 | canvasContainer.addEventListener("mousemove", (e) => { 186 | if (e.target && !(e.target.matches('#defaultCanvas0') || e.target.matches('#drawing-cursor'))) 187 | return 188 | 189 | // to stop wierd cursor artifacts while drawing 190 | e.preventDefault() 191 | 192 | drawingCursor.style.left = (e.clientX - canvasBounds.left - cursorBounds.width / 2) + "px" 193 | drawingCursor.style.top = (e.clientY - canvasBounds.top - cursorBounds.height / 2) + "px" 194 | }) 195 | 196 | document.querySelector("body").addEventListener("mousemove", e => { 197 | if (e.target && (e.target.matches('#defaultCanvas0') || e.target.matches('#drawing-cursor'))) { 198 | drawingCursor.style.display = "inline-block" 199 | cursorBounds = drawingCursor.getBoundingClientRect() 200 | isDrawing = true 201 | } 202 | else { 203 | drawingCursor.style.display = "none" 204 | isDrawing = false 205 | } 206 | }) 207 | 208 | undoBtn.addEventListener("click", () => { 209 | undo() 210 | socket.emit("undo-triggered") 211 | }) 212 | 213 | 214 | redoBtn.addEventListener("click", () => { 215 | redo() 216 | socket.emit("redo-triggered") 217 | }) 218 | 219 | socket.on("undo", () => { 220 | undo() 221 | }) 222 | 223 | socket.on("redo", () => { 224 | redo() 225 | }) 226 | 227 | 228 | function undo() { 229 | if (undoStack.length == 1) 230 | return 231 | 232 | updateRedoStack(undoStack.pop()) 233 | 234 | loadImage(undoStack[undoStack.length - 1], img => { 235 | image(img, 0, 0, canvasWidth, canvasHeight); 236 | }); 237 | } 238 | 239 | function redo() { 240 | if (redoStack.length == 0) 241 | return 242 | 243 | const data = redoStack.pop() 244 | 245 | loadImage(data, img => { 246 | image(img, 0, 0, canvasWidth, canvasHeight); 247 | }); 248 | 249 | saveState(data) 250 | } 251 | 252 | function updateRedoStack(state) { 253 | if (redoStack.length == 10) 254 | redoStack.shift() 255 | 256 | redoStack.push(state) 257 | } -------------------------------------------------------------------------------- /public/js/room.js: -------------------------------------------------------------------------------- 1 | const FIREBASE_KEY = 'AIzaSyDD-SKMBbTcTtvQ1XyzWNKMPxpNrpATZMM'; 2 | const DB_URL = 'https://collabcode-21b1b-default-rtdb.firebaseio.com/'; 3 | 4 | const AGORA_APP_ID = "8b8b3f7547914a1ca9f8b586adc338e3"; 5 | const CHANNEL = window.location.pathname.replace("/", ""); 6 | let TOKEN; 7 | let UID; 8 | 9 | const socket = io("/"); 10 | 11 | 12 | let editor = CodeMirror.fromTextArea(document.querySelector("#firepad"), { 13 | mode: "python", 14 | theme: "dracula", 15 | lineNumbers: true, 16 | extraKeys: { 17 | "Ctrl-Space": "autocomplete" 18 | }, 19 | }) 20 | 21 | editor.on("keyup", function (cm, event) { 22 | if (!cm.state.completionActive && /*Enables keyboard navigation in autocomplete list*/ 23 | event.keyCode > 64 && event.keyCode < 91) {// only when a letter key is pressed 24 | CodeMirror.commands.autocomplete(cm, null, { completeSingle: false }); 25 | } 26 | }); 27 | 28 | async function init() { 29 | // Initialize the Firebase SDK. 30 | firebase.initializeApp({ 31 | apiKey: FIREBASE_KEY, 32 | databaseURL: DB_URL 33 | }); 34 | 35 | // Get Firebase Database reference. 36 | let firepadRef = firebase.database().ref().child(ROOM_ID); 37 | 38 | // Create Firepad (with rich text toolbar and shortcuts enabled). 39 | let firepad = Firepad.fromCodeMirror(firepadRef, editor, { 40 | defaultText: '' 41 | }); 42 | 43 | let runtimeRes = await getRuntimes(); 44 | runtimeRes.forEach(element => { 45 | runtimes[element.language] = element.version; 46 | }); 47 | } 48 | 49 | init(); 50 | 51 | 52 | const client = AgoraRTC.createClient({ 53 | mode: "rtc", 54 | codec: "vp8" 55 | }); 56 | 57 | AgoraRTC.setLogLevel(4); 58 | 59 | let audioTrack; 60 | let videoTrack; 61 | let remoteUsers = {}; 62 | 63 | let runtimes = {}; 64 | 65 | async function fetchCred() { 66 | let response = await fetch(`/getToken/${CHANNEL}`); 67 | let data = await response.json(); 68 | 69 | TOKEN = data.token; 70 | UID = Number(data.uid); 71 | } 72 | 73 | async function joinAndDisplayLocalStream() { 74 | client.on('user-published', handleUserJoined); 75 | client.on('user-left', handleUserLeft); 76 | 77 | await fetchCred(); 78 | 79 | await client.join(AGORA_APP_ID, CHANNEL, TOKEN, UID); 80 | 81 | audioTrack = await AgoraRTC.createMicrophoneAudioTrack(); 82 | videoTrack = await AgoraRTC.createCameraVideoTrack(); 83 | 84 | let player = `
85 |
86 |
`; 87 | 88 | document.getElementById('video-grid').insertAdjacentHTML('beforeend', player); 89 | videoTrack.play(`user-${UID}`); 90 | 91 | audioBtn.disabled = false; 92 | videoBtn.disabled = false; 93 | await delay(1); 94 | await client.publish([audioTrack, videoTrack]); 95 | } 96 | 97 | async function handleUserJoined(user, mediaType) { 98 | remoteUsers[user.uid] = user; 99 | await client.subscribe(user, mediaType); 100 | 101 | if (mediaType === "video") { 102 | let player = document.getElementById(`user-container-${user.uid}`) 103 | if (player != null) { 104 | player.remove(); 105 | } 106 | 107 | player = `
108 |
109 |
`; 110 | 111 | document.getElementById('video-grid').insertAdjacentHTML('beforeend', player); 112 | user.videoTrack.play(`user-${user.uid}`); 113 | 114 | } 115 | if (mediaType === "audio") { 116 | user.audioTrack.play(); 117 | } 118 | } 119 | 120 | async function handleUserLeft(user) { 121 | delete remoteUsers[user.uid]; 122 | document.getElementById(`user-container-${user.uid}`).remove(); 123 | } 124 | 125 | socket.on('connect', function () { 126 | socket.emit("join-room", ROOM_ID, socket.id); 127 | joinAndDisplayLocalStream(); 128 | }); 129 | 130 | socket.on("user-disconnected", userId => { 131 | }) 132 | 133 | 134 | socket.on('user-connected', async userId => { 135 | console.log("New user connected: " + userId); 136 | }) 137 | 138 | const audioBtn = document.getElementById("audio-btn"); 139 | const videoBtn = document.getElementById("video-btn"); 140 | const audioIcon = document.getElementById("audio_icon"); 141 | const videoIcon = document.getElementById("video_icon"); 142 | const shareBtn = document.getElementById("share-btn"); 143 | 144 | audioBtn.addEventListener("click", async () => { 145 | if (audioIcon.innerHTML === "mic_off") { 146 | audioIcon.innerHTML = "mic"; 147 | audioBtn.style.backgroundColor = '#3be8b0'; 148 | audioBtn.style.color = 'black'; 149 | } 150 | else { 151 | audioIcon.innerHTML = "mic_off"; 152 | audioBtn.style.backgroundColor = '#e22929'; 153 | audioBtn.style.color = 'white'; 154 | } 155 | 156 | // Check if audio track exists 157 | if (audioTrack.muted) { 158 | await audioTrack.setMuted(false); 159 | } else { 160 | await audioTrack.setMuted(true); 161 | } 162 | }) 163 | 164 | videoBtn.addEventListener("click", async () => { 165 | if (videoTrack.enabled) { 166 | videoIcon.innerHTML = "videocam_off"; 167 | videoBtn.style.backgroundColor = '#e22929'; 168 | videoBtn.style.color = 'white'; 169 | await videoTrack.setEnabled(false); 170 | } else { 171 | videoIcon.innerHTML = "videocam"; 172 | videoBtn.style.backgroundColor = '#3be8b0'; 173 | videoBtn.style.color = 'black'; 174 | await videoTrack.setEnabled(true); 175 | } 176 | }) 177 | 178 | shareBtn.addEventListener("click", () => { 179 | let input = document.createElement('input'); 180 | input.setAttribute("type", "text"); 181 | input.value = window.location.href; 182 | input.readOnly = true; 183 | 184 | swal("Share code", "Anyone with access to this URL will see your code in real time.", { 185 | content: input, 186 | buttons: { 187 | copy: { 188 | text: "Copy URL", 189 | value: "copy", 190 | } 191 | }, 192 | }) 193 | .then((value) => { 194 | switch (value) { 195 | case "copy": 196 | navigator.clipboard.writeText(window.location.href); 197 | swal("Link copied!"); 198 | break; 199 | } 200 | }); 201 | }) 202 | 203 | const outputDiv = document.getElementById("output-text"); 204 | const languageDropdown = document.getElementById("language-dropdown"); 205 | const inputDiv = document.getElementById("input"); 206 | const runCodeBtn = document.getElementById("run-code-btn"); 207 | 208 | socket.on("code-output", output => { 209 | outputDiv.innerHTML = output; 210 | }); 211 | 212 | function runCode() { 213 | let code = editor.getValue(); 214 | let language = languageDropdown.value; 215 | let input = inputDiv.value; 216 | let spinner = document.querySelector(".spin"); 217 | 218 | if (code) { 219 | spinner.style.display = "inline-block"; 220 | outputDiv.innerHTML = ""; 221 | var myHeaders = new Headers(); 222 | myHeaders.append("Content-Type", "application/json"); 223 | 224 | var raw = JSON.stringify({ 225 | "language": language, 226 | "version": runtimes[language], 227 | "files": [ 228 | { 229 | "content": `${code}` 230 | } 231 | ], 232 | "stdin": input, 233 | "compile_timeout": 10000, 234 | "run_timeout": 3000, 235 | "compile_memory_limit": -1, 236 | "run_memory_limit": -1 237 | }); 238 | 239 | var requestOptions = { 240 | method: 'POST', 241 | headers: myHeaders, 242 | body: raw, 243 | redirect: 'follow' 244 | }; 245 | 246 | fetch("https://emkc.org/api/v2/piston/execute", requestOptions) 247 | .then(response => response.text()) 248 | .then(result => { 249 | let output = JSON.parse(result).run.output; 250 | spinner.style.display = ""; 251 | outputDiv.innerHTML = output; 252 | socket.emit("code-output", output); 253 | }) 254 | .catch(error => console.log('error', error)); 255 | } 256 | } 257 | 258 | runCodeBtn.addEventListener("click", runCode); 259 | languageDropdown.addEventListener("change", () => { 260 | socket.emit("language-change", languageDropdown.value); 261 | changeEditorMode(languageDropdown.value); 262 | }) 263 | 264 | socket.on("language-change", language => { 265 | languageDropdown.value = language; 266 | changeEditorMode(language); 267 | }); 268 | 269 | async function getRuntimes() { 270 | var myHeaders = new Headers(); 271 | 272 | var requestOptions = { 273 | method: 'GET', 274 | headers: myHeaders, 275 | redirect: 'follow' 276 | }; 277 | 278 | let response = await fetch("https://emkc.org/api/v2/piston/runtimes", requestOptions); 279 | 280 | response = await response.text(); 281 | return JSON.parse(response); 282 | } 283 | 284 | function changeEditorMode(language) { 285 | if (language === "python" || language === "javascript") { 286 | editor.setOption("mode", language); 287 | } 288 | else if (language === "java") { 289 | editor.setOption("mode", "text/x-java"); 290 | } 291 | else if (language === "c") { 292 | editor.setOption("mode", "text/x-csrc"); 293 | } 294 | else if (language === "c++") { 295 | editor.setOption("mode", "text/x-c++src"); 296 | } 297 | else if (language === "csharp") { 298 | editor.setOption("mode", "text/x-csharp"); 299 | } 300 | else if (language === "go") { 301 | editor.setOption("mode", "text/x-go"); 302 | } 303 | } 304 | 305 | function delay(n) { 306 | return new Promise(function (resolve) { 307 | setTimeout(resolve, n * 1000); 308 | }); 309 | } -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # CollabCode 2 | 3 | A collaborative, real-time, online coding environment for developers. 4 | 5 | 6 | 7 | ## Features 8 | 9 | - Feature rich code editor 10 | - The CollabCode code editor supports 7+ different languages, and comes with syntax highlighting and auto-complete. 11 | - In browser compiler 12 | - Compile or run your code in the browser using the high performance CollabCode compiler. 13 | - Video and audio chat 14 | - Chat with your fellow collaborators using the inbuilt video and audio chat. 15 | 16 | 17 | 18 | ## Try it! 19 | 20 | Try out CollabCode [here](https://collabcode.onrender.com/). 21 | 22 | 23 | 24 | ## Glimpse 25 | 26 | ![](https://raw.githubusercontent.com/Swanand01/CollabCode/master/public/glimpse.png) 27 | 28 | 29 | 30 | ## Tech 31 | 32 | **Server:** NodeJS, ExpressJS, SocketIO. 33 | 34 | **Client:** HTML, CSS, JS, ejs. 35 | 36 | **CollabCode Editor**: [Codemirror](https://codemirror.net/) and [Firepad](https://firepad.io/). 37 | 38 | **CollabCode Compiler**: [Piston](https://github.com/engineer-man/piston) API. 39 | 40 | **Video and audio chat**: [Agora](https://www.agora.io/en/). 41 | 42 | 43 | 44 | ## Run locally 45 | 46 | 1. Clone the repository 47 | 48 | `https://github.com/Swanand01/CollabCode.git` 49 | 50 | 2. Install dependencies 51 | 52 | `npm install` 53 | 54 | 3. Create a Agora project. [Agora Docs](https://docs.agora.io/en/Agora%20Platform/get_appid_token?platform=Web). 55 | 56 | 4. Create a .env file in the root directory, and set the following constants: 57 | 58 | `NODE_ENV = development` 59 | 60 | `APP_ID = "YOUR_AGORA_APP_ID"` 61 | 62 | `APP_CERTIFICATE = "YOUR_AGORA_APP_CERTIFICATE"` 63 | 64 | 5. Create a [Firebase](https://console.firebase.google.com/) project. 65 | 66 | 6. Create a Realtime database. Please make sure to select the location as US-Central. 67 | 68 | 7. In `room.js` , set the following constants: 69 | 70 | `const FIREBASE_KEY = 'YOUR_FIREBASE_KEY';` 71 | 72 | `const DB_URL = 'YOUR_FIREBASE_DB_URL';` 73 | 74 | `const AGORA_APP_ID = "YOUR_AGORA_APP_ID";` 75 | 76 | 8. Run `npm run devStart` . 77 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | if (process.env.NODE_ENV !== 'production') { 2 | require('dotenv').config(); 3 | } 4 | 5 | const appID = process.env.APP_ID 6 | const appCertificate = process.env.APP_CERTIFICATE 7 | 8 | const express = require('express') 9 | const app = express() 10 | const server = require("http").Server(app); 11 | const io = require("socket.io")(server); 12 | const { RtcTokenBuilder, RtcRole } = require('agora-access-token'); 13 | 14 | app.set("view engine", "ejs"); 15 | app.use(express.static("public")); 16 | 17 | app.get("/", (req, res) => { 18 | res.render("home", {}); 19 | }); 20 | 21 | app.get("/:room", (req, res) => { 22 | res.render("room", { 23 | roomId: req.params.room 24 | }); 25 | }); 26 | 27 | app.get("/getToken/:channel", (req, res) => { 28 | const channelName = req.params.channel; 29 | const uid = getRandInteger(1, 230); 30 | const role = RtcRole.PUBLISHER; 31 | const expirationTimeInSeconds = 3600 * 24; 32 | const currentTimestamp = Math.floor(Date.now() / 1000); 33 | const privilegeExpiredTs = currentTimestamp + expirationTimeInSeconds; 34 | 35 | const token = RtcTokenBuilder.buildTokenWithUid(appID, appCertificate, channelName, uid, role, privilegeExpiredTs); 36 | 37 | res.json({ 38 | token: token, 39 | uid: uid 40 | }); 41 | }) 42 | 43 | 44 | io.on("connection", socket => { 45 | socket.on("join-room", (roomId, userId) => { 46 | socket.join(roomId); 47 | socket.broadcast.to(roomId).emit("user-connected", userId); 48 | 49 | socket.on("disconnect", () => { 50 | socket.broadcast.to(roomId).emit("user-disconnected", userId); 51 | }) 52 | 53 | socket.on("code-change", (delta) => { 54 | socket.broadcast.to(roomId).emit("code-change", delta); 55 | }) 56 | 57 | socket.on("code-output", (output) => { 58 | socket.broadcast.to(roomId).emit("code-output", output); 59 | }) 60 | 61 | socket.on("language-change", (language) => { 62 | socket.broadcast.to(roomId).emit("language-change", language); 63 | }) 64 | 65 | // live board related events 66 | let clientSet = io.sockets.adapter.rooms.get(roomId) 67 | let host 68 | if (clientSet) 69 | host = [...clientSet][0] 70 | 71 | socket.join(roomId) 72 | 73 | if (host) 74 | socket.to(host).emit('send-state', socket.id) 75 | 76 | socket.on("send-canvas-state", (user, data, undoStack, redoStack) => { 77 | socket.to(user).emit("get-canvas-state", data, undoStack, redoStack) 78 | }) 79 | 80 | socket.on("trigger-clear-canvas", roomId => { 81 | socket.to(roomId).emit('clear-canvas') 82 | }) 83 | 84 | socket.on("send-paint-path", paintObject => { 85 | socket.to(roomId).emit("paint", paintObject) 86 | }) 87 | 88 | socket.on("undo-triggered", () => { 89 | socket.to(roomId).emit("undo") 90 | }) 91 | 92 | socket.on("redo-triggered", () => { 93 | socket.to(roomId).emit("redo") 94 | }) 95 | }) 96 | }) 97 | 98 | server.listen(3000); 99 | 100 | function getRandInteger(min, max) { 101 | return Math.floor(Math.random() * (max - min)) + min; 102 | } -------------------------------------------------------------------------------- /views/home.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | CollabCode 8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 18 |
19 | 20 |
21 |
22 |

Share code and collaborate in real-time.

23 |

An online coding environment for interviews, troubleshooting, teaching & more…

24 |

25 | Free to use and 26 | 27 | open-source. 28 | 29 |

30 |
31 | 32 |
33 | 34 |
35 |
36 |
37 |

38 | Feature rich code editor 39 |

40 |
41 | The CollabCode code editor supports 7+ different languages, and comes with syntax highlighting and auto-complete. 42 |
43 |
44 |
45 |

46 | In browser compiler 47 |

48 |
49 | Compile or run your code in the browser using the high performance CollabCode compiler. 50 |
51 |
52 |
53 |

54 | Video and audio chat 55 |

56 |
57 | Chat with your fellow collaborators using the inbuilt video and audio chat. 58 |
59 |
60 |
61 |
62 | 63 |
64 | 69 |
70 | 71 | 72 | -------------------------------------------------------------------------------- /views/room.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | CollabCode 8 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 |
52 | 61 |
62 |
63 |
64 |
65 | 74 |
75 | 76 |
77 |
78 | 79 |
80 |
81 |
82 | Output will appear here. 83 |
84 |
85 |
86 |
87 | 88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 | 97 |
98 | 99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 | 108 |
109 |
110 |
111 |
112 | 115 | 118 |
119 |
120 | 121 | 122 | 123 | 124 | --------------------------------------------------------------------------------