RabsRincon | 6ef87b5 | 2018-02-27 14:58:21 +0100 | [diff] [blame] | 1 | // Copyright (c) 2017, Matt Godbolt |
Matt Godbolt | eb74776 | 2017-01-04 17:12:52 -0600 | [diff] [blame] | 2 | // All rights reserved. |
| 3 | // |
| 4 | // Redistribution and use in source and binary forms, with or without |
| 5 | // modification, are permitted provided that the following conditions are met: |
| 6 | // |
| 7 | // * Redistributions of source code must retain the above copyright notice, |
| 8 | // this list of conditions and the following disclaimer. |
| 9 | // * Redistributions in binary form must reproduce the above copyright |
| 10 | // notice, this list of conditions and the following disclaimer in the |
| 11 | // documentation and/or other materials provided with the distribution. |
| 12 | // |
| 13 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" |
| 14 | // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| 15 | // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
| 16 | // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE |
| 17 | // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
| 18 | // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
| 19 | // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
| 20 | // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
| 21 | // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
| 22 | // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
| 23 | // POSSIBILITY OF SUCH DAMAGE. |
| 24 | |
jaredwy | 8be70d1 | 2017-12-05 17:03:07 +1100 | [diff] [blame] | 25 | "use strict"; |
Matt Godbolt | eb74776 | 2017-01-04 17:12:52 -0600 | [diff] [blame] | 26 | |
RabsRincon | 79f3e90 | 2018-07-16 07:48:47 +0200 | [diff] [blame] | 27 | var FontScale = require('../fontscale'); |
RabsRincon | 6a9095f | 2019-07-22 14:37:49 +0200 | [diff] [blame] | 28 | var monaco = require('monaco-editor'); |
jaredwy | 8be70d1 | 2017-12-05 17:03:07 +1100 | [diff] [blame] | 29 | var _ = require('underscore'); |
| 30 | var $ = require('jquery'); |
RabsRincon | 79f3e90 | 2018-07-16 07:48:47 +0200 | [diff] [blame] | 31 | var ga = require('../analytics'); |
Matt Godbolt | 476df0f | 2017-02-02 08:35:08 -0600 | [diff] [blame] | 32 | |
RabsRincon | 79f3e90 | 2018-07-16 07:48:47 +0200 | [diff] [blame] | 33 | require('../modes/asm-mode'); |
jaredwy | 8be70d1 | 2017-12-05 17:03:07 +1100 | [diff] [blame] | 34 | require('selectize'); |
Matt Godbolt | eb74776 | 2017-01-04 17:12:52 -0600 | [diff] [blame] | 35 | |
jaredwy | 8be70d1 | 2017-12-05 17:03:07 +1100 | [diff] [blame] | 36 | function State(id, model) { |
| 37 | this.id = id; |
| 38 | this.model = model; |
| 39 | this.compiler = null; |
| 40 | this.result = null; |
| 41 | } |
Matt Godbolt | eb74776 | 2017-01-04 17:12:52 -0600 | [diff] [blame] | 42 | |
jaredwy | 8be70d1 | 2017-12-05 17:03:07 +1100 | [diff] [blame] | 43 | State.prototype.update = function (id, compiler, result) { |
| 44 | if (this.id !== id) return false; |
| 45 | this.compiler = compiler; |
| 46 | this.result = result; |
| 47 | var asm = result.asm || []; |
| 48 | this.model.setValue(_.pluck(asm, 'text').join("\n")); |
| 49 | return true; |
| 50 | }; |
Matt Godbolt | eb74776 | 2017-01-04 17:12:52 -0600 | [diff] [blame] | 51 | |
jaredwy | 8be70d1 | 2017-12-05 17:03:07 +1100 | [diff] [blame] | 52 | function Diff(hub, container, state) { |
| 53 | this.container = container; |
| 54 | this.eventHub = hub.createEventHub(); |
| 55 | this.domRoot = container.getElement(); |
| 56 | this.domRoot.html($('#diff').html()); |
| 57 | this.compilers = {}; |
Matt Godbolt | eb74776 | 2017-01-04 17:12:52 -0600 | [diff] [blame] | 58 | |
jaredwy | 8be70d1 | 2017-12-05 17:03:07 +1100 | [diff] [blame] | 59 | this.outputEditor = monaco.editor.createDiffEditor(this.domRoot.find(".monaco-placeholder")[0], { |
RabsRincon | 46244d5 | 2018-02-15 19:19:59 +0100 | [diff] [blame] | 60 | fontFamily: 'Consolas, "Liberation Mono", Courier, monospace', |
jaredwy | 8be70d1 | 2017-12-05 17:03:07 +1100 | [diff] [blame] | 61 | scrollBeyondLastLine: false, |
| 62 | readOnly: true, |
| 63 | language: 'asm' |
| 64 | }); |
Matt Godbolt | eb74776 | 2017-01-04 17:12:52 -0600 | [diff] [blame] | 65 | |
jaredwy | 8be70d1 | 2017-12-05 17:03:07 +1100 | [diff] [blame] | 66 | this.lhs = new State(state.lhs, monaco.editor.createModel('', 'asm')); |
| 67 | this.rhs = new State(state.rhs, monaco.editor.createModel('', 'asm')); |
| 68 | this.outputEditor.setModel({original: this.lhs.model, modified: this.rhs.model}); |
Matt Godbolt | 37c6291 | 2017-02-04 08:46:15 -0600 | [diff] [blame] | 69 | |
jaredwy | 8be70d1 | 2017-12-05 17:03:07 +1100 | [diff] [blame] | 70 | var selectize = this.domRoot.find(".diff-picker").selectize({ |
| 71 | sortField: 'name', |
| 72 | valueField: 'id', |
| 73 | labelField: 'name', |
| 74 | searchField: ['name'], |
| 75 | options: [], |
| 76 | items: [], |
| 77 | render: { |
| 78 | option: function (item, escape) { |
| 79 | return '<div>' + |
| 80 | '<span class="compiler">' + escape(item.compiler.name) + '</span>' + |
| 81 | '<span class="options">' + escape(item.options) + '</span>' + |
| 82 | '<ul class="meta">' + |
| 83 | '<li class="editor">Editor #' + escape(item.editorId) + '</li>' + |
| 84 | '<li class="compilerId">Compiler #' + escape(item.id) + '</li>' + |
| 85 | '</ul></div>'; |
Matt Godbolt | 60e1a86 | 2017-03-26 08:37:59 -0500 | [diff] [blame] | 86 | } |
RabsRincon | a9ceec4 | 2018-10-07 16:44:19 +0200 | [diff] [blame] | 87 | }, |
| 88 | dropdownParent: 'body' |
jaredwy | 8be70d1 | 2017-12-05 17:03:07 +1100 | [diff] [blame] | 89 | }).on('change', _.bind(function (e) { |
| 90 | var target = $(e.target); |
| 91 | var compiler = this.compilers[target.val()]; |
Matt Godbolt | 476df0f | 2017-02-02 08:35:08 -0600 | [diff] [blame] | 92 | if (!compiler) return; |
jaredwy | 8be70d1 | 2017-12-05 17:03:07 +1100 | [diff] [blame] | 93 | if (target.hasClass('lhs')) { |
| 94 | this.lhs.compiler = compiler; |
| 95 | this.lhs.id = compiler.id; |
| 96 | } else { |
| 97 | this.rhs.compiler = compiler; |
| 98 | this.rhs.id = compiler.id; |
| 99 | } |
| 100 | this.onDiffSelect(compiler.id); |
| 101 | }, this)); |
| 102 | this.selectize = {lhs: selectize[0].selectize, rhs: selectize[1].selectize}; |
| 103 | |
RabsRincon | 8a24a39 | 2018-03-25 19:04:48 +0200 | [diff] [blame] | 104 | this.initButtons(state); |
| 105 | this.initCallbacks(); |
jaredwy | 8be70d1 | 2017-12-05 17:03:07 +1100 | [diff] [blame] | 106 | |
| 107 | this.updateCompilerNames(); |
| 108 | this.updateCompilers(); |
RabsRincon | f6e9c63 | 2018-07-15 20:08:00 +0200 | [diff] [blame] | 109 | ga.proxy('send', { |
| 110 | hitType: 'event', |
RabsRincon | 9845018 | 2018-07-28 19:39:54 +0200 | [diff] [blame] | 111 | eventCategory: 'OpenViewPane', |
| 112 | eventAction: 'Diff' |
RabsRincon | f6e9c63 | 2018-07-15 20:08:00 +0200 | [diff] [blame] | 113 | }); |
jaredwy | 8be70d1 | 2017-12-05 17:03:07 +1100 | [diff] [blame] | 114 | } |
| 115 | |
| 116 | // TODO: de-dupe with compiler etc |
| 117 | Diff.prototype.resize = function () { |
RabsRincon | c6e4285 | 2018-03-28 20:59:50 +0200 | [diff] [blame] | 118 | var topBarHeight = this.topBar.outerHeight(true); |
jaredwy | 8be70d1 | 2017-12-05 17:03:07 +1100 | [diff] [blame] | 119 | this.outputEditor.layout({ |
| 120 | width: this.domRoot.width(), |
| 121 | height: this.domRoot.height() - topBarHeight |
| 122 | }); |
| 123 | }; |
| 124 | |
| 125 | Diff.prototype.onDiffSelect = function (id) { |
| 126 | this.eventHub.emit('resendCompilation', id); |
| 127 | this.updateCompilerNames(); |
| 128 | this.updateState(); |
| 129 | }; |
| 130 | |
| 131 | Diff.prototype.onCompileResult = function (id, compiler, result) { |
| 132 | // both sides must be updated, don't be tempted to rewrite this as |
| 133 | // var changes = lhs.update() || rhs.update(); |
| 134 | var lhsChanged = this.lhs.update(id, compiler, result); |
| 135 | var rhsChanged = this.rhs.update(id, compiler, result); |
| 136 | if (lhsChanged || rhsChanged) { |
| 137 | this.updateCompilerNames(); |
| 138 | } |
| 139 | }; |
| 140 | |
RabsRincon | 8a24a39 | 2018-03-25 19:04:48 +0200 | [diff] [blame] | 141 | Diff.prototype.initButtons = function (state) { |
| 142 | this.fontScale = new FontScale(this.domRoot, state, this.outputEditor); |
RabsRincon | c6e4285 | 2018-03-28 20:59:50 +0200 | [diff] [blame] | 143 | |
| 144 | this.topBar = this.domRoot.find(".top-bar"); |
RabsRincon | 8a24a39 | 2018-03-25 19:04:48 +0200 | [diff] [blame] | 145 | }; |
| 146 | |
| 147 | Diff.prototype.initCallbacks = function () { |
| 148 | this.fontScale.on('change', _.bind(this.updateState, this)); |
| 149 | |
| 150 | this.eventHub.on('compileResult', this.onCompileResult, this); |
| 151 | this.eventHub.on('compiler', this.onCompiler, this); |
| 152 | this.eventHub.on('compilerClose', this.onCompilerClose, this); |
| 153 | this.eventHub.on('settingsChange', this.onSettingsChange, this); |
| 154 | this.eventHub.on('themeChange', this.onThemeChange, this); |
| 155 | this.container.on('destroy', function () { |
| 156 | this.eventHub.unsubscribe(); |
| 157 | this.outputEditor.dispose(); |
| 158 | }, this); |
| 159 | this.container.on('resize', this.resize, this); |
| 160 | this.container.on('shown', this.resize, this); |
| 161 | |
| 162 | this.eventHub.emit('resendCompilation', this.lhs.id); |
| 163 | this.eventHub.emit('resendCompilation', this.rhs.id); |
| 164 | this.eventHub.emit('findCompilers'); |
| 165 | this.eventHub.emit('requestTheme'); |
| 166 | this.eventHub.emit('requestSettings'); |
| 167 | }; |
| 168 | |
jaredwy | 8be70d1 | 2017-12-05 17:03:07 +1100 | [diff] [blame] | 169 | Diff.prototype.onCompiler = function (id, compiler, options, editorId) { |
| 170 | if (!compiler) return; |
| 171 | options = options || ""; |
| 172 | var name = compiler.name + " " + options; |
| 173 | // TODO: selectize doesn't play nicely with CSS tricks for truncation; this is the best I can do |
| 174 | // There's a plugin at: http://www.benbybenjacobs.com/blog/2014/04/09/no-wrap-plugin-for-selectize-dot-js |
| 175 | // but it doesn't look easy to integrate. |
| 176 | var maxLength = 30; |
| 177 | if (name.length > maxLength - 3) name = name.substr(0, maxLength - 3) + "..."; |
| 178 | this.compilers[id] = { |
| 179 | id: id, |
| 180 | name: name, |
| 181 | options: options, |
| 182 | editorId: editorId, |
| 183 | compiler: compiler |
| 184 | }; |
| 185 | if (!this.lhs.id) { |
| 186 | this.lhs.compiler = this.compilers[id]; |
| 187 | this.lhs.id = id; |
| 188 | this.onDiffSelect(id); |
| 189 | } else if (!this.rhs.id) { |
| 190 | this.rhs.compiler = this.compilers[id]; |
| 191 | this.rhs.id = id; |
| 192 | this.onDiffSelect(id); |
| 193 | } |
| 194 | this.updateCompilers(); |
| 195 | }; |
| 196 | |
| 197 | Diff.prototype.onCompilerClose = function (id) { |
| 198 | delete this.compilers[id]; |
| 199 | this.updateCompilers(); |
| 200 | }; |
| 201 | |
| 202 | Diff.prototype.updateCompilerNames = function () { |
| 203 | var name = "Diff"; |
| 204 | if (this.lhs.compiler && this.rhs.compiler) |
| 205 | name += " " + this.lhs.compiler.name + " vs " + this.rhs.compiler.name; |
| 206 | this.container.setTitle(name); |
| 207 | }; |
| 208 | |
| 209 | Diff.prototype.updateCompilersFor = function (selectize, id) { |
| 210 | selectize.clearOptions(); |
| 211 | _.each(this.compilers, function (compiler) { |
| 212 | selectize.addOption(compiler); |
| 213 | }, this); |
| 214 | if (this.compilers[id]) { |
| 215 | selectize.setValue(id); |
| 216 | } |
| 217 | }; |
| 218 | |
| 219 | Diff.prototype.updateCompilers = function () { |
| 220 | this.updateCompilersFor(this.selectize.lhs, this.lhs.id); |
| 221 | this.updateCompilersFor(this.selectize.rhs, this.rhs.id); |
| 222 | }; |
| 223 | |
| 224 | Diff.prototype.updateState = function () { |
| 225 | var state = { |
| 226 | lhs: this.lhs.id, |
| 227 | rhs: this.rhs.id |
| 228 | }; |
| 229 | this.fontScale.addState(state); |
| 230 | this.container.setState(state); |
| 231 | }; |
| 232 | |
| 233 | Diff.prototype.onThemeChange = function (newTheme) { |
| 234 | if (this.outputEditor) |
| 235 | this.outputEditor.updateOptions({theme: newTheme.monaco}); |
| 236 | }; |
| 237 | |
RabsRincon | abb391f | 2018-03-24 23:12:27 +0100 | [diff] [blame] | 238 | Diff.prototype.onSettingsChange = function (newSettings) { |
| 239 | this.outputEditor.updateOptions({ |
| 240 | minimap: { |
| 241 | enabled: newSettings.showMinimap |
RabsRincon | 5706da4 | 2018-12-26 21:45:38 +0100 | [diff] [blame] | 242 | }, |
Rubén Rincón | 6e4ec41 | 2019-11-01 16:25:37 +0100 | [diff] [blame] | 243 | fontFamily: newSettings.editorsFFont, |
Rubén Rincón | 9b31ce2 | 2019-11-04 16:47:14 +0100 | [diff] [blame] | 244 | fontLigatures: newSettings.editorsFLigatures |
RabsRincon | abb391f | 2018-03-24 23:12:27 +0100 | [diff] [blame] | 245 | }); |
| 246 | }; |
| 247 | |
RabsRincon | 62e6830 | 2018-02-07 23:58:21 +0100 | [diff] [blame] | 248 | module.exports = { |
jaredwy | 8be70d1 | 2017-12-05 17:03:07 +1100 | [diff] [blame] | 249 | Diff: Diff, |
| 250 | getComponent: function (lhs, rhs) { |
| 251 | return { |
| 252 | type: 'component', |
| 253 | componentName: 'diff', |
RabsRincon | 62e6830 | 2018-02-07 23:58:21 +0100 | [diff] [blame] | 254 | componentState: {lhs: lhs, rhs: rhs} |
Matt Godbolt | 60e1a86 | 2017-03-26 08:37:59 -0500 | [diff] [blame] | 255 | }; |
jaredwy | 8be70d1 | 2017-12-05 17:03:07 +1100 | [diff] [blame] | 256 | } |
| 257 | }; |