diff --git a/src/app/editor/SourceHighlighters.js b/src/app/editor/SourceHighlighters.js index d484f04689..92f2ee6649 100644 --- a/src/app/editor/SourceHighlighters.js +++ b/src/app/editor/SourceHighlighters.js @@ -1,7 +1,7 @@ 'use strict' -var SourceHighlighter = require('./sourceHighlighter') +const SourceHighlighter = require('./sourceHighlighter') -module.exports = class SourceHighlighters { +class SourceHighlighters { constructor () { this.highlighters = {} @@ -17,7 +17,7 @@ module.exports = class SourceHighlighters { // TODO what to do with mod? highlight (mod, lineColumnPos, filePath, hexColor, cb) { - var position + let position try { position = JSON.parse(lineColumnPos) } catch (e) { @@ -34,3 +34,5 @@ module.exports = class SourceHighlighters { cb() } } + +module.exports = SourceHighlighters diff --git a/src/app/editor/contextView.js b/src/app/editor/contextView.js index 671538a043..5d9c09513f 100644 --- a/src/app/editor/contextView.js +++ b/src/app/editor/contextView.js @@ -1,10 +1,10 @@ 'use strict' -var yo = require('yo-yo') -var remixLib = require('remix-lib') -var SourceMappingDecoder = remixLib.SourceMappingDecoder -var globalRegistry = require('../../global/registry') +const yo = require('yo-yo') +const remixLib = require('remix-lib') +const SourceMappingDecoder = remixLib.SourceMappingDecoder +const globalRegistry = require('../../global/registry') -var css = require('./styles/contextView-styles') +const css = require('./styles/contextView-styles') /* Display information about the current focused code: @@ -15,30 +15,29 @@ var css = require('./styles/contextView-styles') */ class ContextView { constructor (opts, localRegistry) { - const self = this - self._components = {} - self._components.registry = localRegistry || globalRegistry - self.contextualListener = opts.contextualListener - self.editor = opts.editor - self._deps = { - compilersArtefacts: self._components.registry.get('compilersartefacts').api, - offsetToLineColumnConverter: self._components.registry.get('offsettolinecolumnconverter').api, - config: self._components.registry.get('config').api, - fileManager: self._components.registry.get('filemanager').api + this._components = {} + this._components.registry = localRegistry || globalRegistry + this.contextualListener = opts.contextualListener + this.editor = opts.editor + this._deps = { + compilersArtefacts: this._components.registry.get('compilersartefacts').api, + offsetToLineColumnConverter: this._components.registry.get('offsettolinecolumnconverter').api, + config: this._components.registry.get('config').api, + fileManager: this._components.registry.get('filemanager').api } this._view this._nodes this._current this.sourceMappingDecoder = new SourceMappingDecoder() this.previousElement = null - self.contextualListener.event.register('contextChanged', nodes => { + this.contextualListener.event.register('contextChanged', nodes => { this._nodes = nodes this.update() }) } render () { - var view = yo`
+ const view = yo`
${this._renderTarget()}
@@ -70,13 +69,14 @@ class ContextView { } _renderTarget () { - var previous = this._current + let last + const previous = this._current if (this._nodes && this._nodes.length) { - var last = this._nodes[this._nodes.length - 1] + last = this._nodes[this._nodes.length - 1] if (isDefinition(last)) { this._current = last } else { - var target = this.contextualListener.declarationOf(last) + const target = this.contextualListener.declarationOf(last) if (target) { this._current = target } else { @@ -91,27 +91,26 @@ class ContextView { } _jumpToInternal (position) { - var self = this - function jumpToLine (lineColumn) { + const jumpToLine = (lineColumn) => { if (lineColumn.start && lineColumn.start.line && lineColumn.start.column) { - self.editor.gotoLine(lineColumn.start.line, lineColumn.end.column + 1) + this.editor.gotoLine(lineColumn.start.line, lineColumn.end.column + 1) } } - let lastCompilationResult = self._deps.compilersArtefacts['__last'] + let lastCompilationResult = this._deps.compilersArtefacts['__last'] if (lastCompilationResult && lastCompilationResult.data) { - var lineColumn = self._deps.offsetToLineColumnConverter.offsetToLineColumn( + const lineColumn = this._deps.offsetToLineColumnConverter.offsetToLineColumn( position, position.file, lastCompilationResult.getSourceCode().sources, lastCompilationResult.getAsts()) - var filename = lastCompilationResult.getSourceName(position.file) + const filename = lastCompilationResult.getSourceName(position.file) // TODO: refactor with rendererAPI.errorClick - if (filename !== self._deps.config.get('currentFile')) { - var provider = self._deps.fileManager.fileProviderOf(filename) + if (filename !== this._deps.config.get('currentFile')) { + const provider = this._deps.fileManager.fileProviderOf(filename) if (provider) { provider.exists(filename, (error, exist) => { if (error) return console.log(error) - self._deps.fileManager.switchFile(filename) + this._deps.fileManager.switchFile(filename) jumpToLine(lineColumn) }) } @@ -123,14 +122,13 @@ class ContextView { _render (node, nodeAtCursorPosition) { if (!node) return yo`
` - var self = this - var references = self.contextualListener.referencesOf(node) - var type = (node.attributes && node.attributes.type) ? node.attributes.type : node.name + let references = this.contextualListener.referencesOf(node) + const type = (node.attributes && node.attributes.type) ? node.attributes.type : node.name references = `${references ? references.length : '0'} reference(s)` - var ref = 0 - var nodes = self.contextualListener.getActiveHighlights() - for (var k in nodes) { + let ref = 0 + const nodes = this.contextualListener.getActiveHighlights() + for (const k in nodes) { if (nodeAtCursorPosition.id === nodes[k].nodeId) { ref = k break @@ -138,22 +136,35 @@ class ContextView { } // JUMP BETWEEN REFERENCES - function jump (e) { + const jump = (e) => { e.target.dataset.action === 'next' ? ref++ : ref-- if (ref < 0) ref = nodes.length - 1 if (ref >= nodes.length) ref = 0 - self._jumpToInternal(nodes[ref].position) + this._jumpToInternal(nodes[ref].position) } - function jumpTo () { + const jumpTo = () => { if (node && node.src) { - var position = self.sourceMappingDecoder.decode(node.src) + const position = this.sourceMappingDecoder.decode(node.src) if (position) { - self._jumpToInternal(position) + this._jumpToInternal(position) } } } + const showGasEstimation = () => { + if (node.name === 'FunctionDefinition') { + const result = this.contextualListener.gasEstimation(node) + const executionCost = 'Execution cost: ' + result.executionCost + ' gas' + const codeDepositCost = 'Code deposit cost: ' + result.codeDepositCost + ' gas' + const estimatedGas = result.codeDepositCost ? `${codeDepositCost}, ${executionCost}` : `${executionCost}` + return yo`
+ + ${estimatedGas} +
` + } + } + return yo`
${type}
${node.attributes.name}
@@ -163,19 +174,6 @@ class ContextView { ${showGasEstimation()}
` - - function showGasEstimation () { - if (node.name === 'FunctionDefinition') { - var result = self.contextualListener.gasEstimation(node) - var executionCost = 'Execution cost: ' + result.executionCost + ' gas' - var codeDepositCost = 'Code deposit cost: ' + result.codeDepositCost + ' gas' - var estimatedGas = result.codeDepositCost ? `${codeDepositCost}, ${executionCost}` : `${executionCost}` - return yo`
- - ${estimatedGas} -
` - } - } } } diff --git a/src/app/editor/contextualListener.js b/src/app/editor/contextualListener.js index ed33c4b15d..5853c36f45 100644 --- a/src/app/editor/contextualListener.js +++ b/src/app/editor/contextualListener.js @@ -1,25 +1,24 @@ 'use strict' -var remixLib = require('remix-lib') -var SourceMappingDecoder = remixLib.SourceMappingDecoder -var AstWalker = remixLib.AstWalker -var EventManager = require('../../lib/events') -var globalRegistry = require('../../global/registry') +const remixLib = require('remix-lib') +const SourceMappingDecoder = remixLib.SourceMappingDecoder +const AstWalker = remixLib.AstWalker +const EventManager = require('../../lib/events') +const globalRegistry = require('../../global/registry') /* trigger contextChanged(nodes) */ class ContextualListener { constructor (opts, localRegistry) { - var self = this this.event = new EventManager() - self._components = {} - self._components.registry = localRegistry || globalRegistry - self.editor = opts.editor - self.pluginManager = opts.pluginManager - self._deps = { - compilersArtefacts: self._components.registry.get('compilersartefacts').api, - config: self._components.registry.get('config').api, - offsetToLineColumnConverter: self._components.registry.get('offsettolinecolumnconverter').api + this._components = {} + this._components.registry = localRegistry || globalRegistry + this.editor = opts.editor + this.pluginManager = opts.pluginManager + this._deps = { + compilersArtefacts: this._components.registry.get('compilersartefacts').api, + config: this._components.registry.get('config').api, + offsetToLineColumnConverter: this._components.registry.get('offsettolinecolumnconverter').api } this._index = { Declarations: {}, @@ -27,7 +26,7 @@ class ContextualListener { } this._activeHighlights = [] - self.pluginManager.event.register('sendCompilationResult', (file, source, languageVersion, data) => { + this.pluginManager.event.register('sendCompilationResult', (file, source, languageVersion, data) => { this._stopHighlighting() this._index = { Declarations: {}, @@ -36,13 +35,13 @@ class ContextualListener { this._buildIndex(data, source) }) - self.editor.event.register('contentChanged', () => { this._stopHighlighting() }) + this.editor.event.register('contentChanged', () => { this._stopHighlighting() }) this.sourceMappingDecoder = new SourceMappingDecoder() this.astWalker = new AstWalker() setInterval(() => { - if (self._deps.compilersArtefacts['__last']) { - this._highlightItems(self.editor.getCursorPosition(), self._deps.compilersArtefacts['__last'], self._deps.config.get('currentFile')) + if (this._deps.compilersArtefacts['__last']) { + this._highlightItems(this.editor.getCursorPosition(), this._deps.compilersArtefacts['__last'], this._deps.config.get('currentFile')) } }, 1000) } @@ -73,7 +72,7 @@ class ContextualListener { this.currentPosition = cursorPosition this.currentFile = file if (compilationResult && compilationResult.data && compilationResult.data.sources[file]) { - var nodes = this.sourceMappingDecoder.nodesAtPosition(null, cursorPosition, compilationResult.data.sources[file]) + const nodes = this.sourceMappingDecoder.nodesAtPosition(null, cursorPosition, compilationResult.data.sources[file]) this.nodes = nodes if (nodes && nodes.length && nodes[nodes.length - 1]) { this._highlightExpressions(nodes[nodes.length - 1], compilationResult) @@ -84,19 +83,18 @@ class ContextualListener { _buildIndex (compilationResult, source) { if (compilationResult && compilationResult.sources) { - var self = this - var callback = {} - callback['*'] = function (node) { + const callback = {} + callback['*'] = (node) => { if (node && node.attributes && node.attributes.referencedDeclaration) { - if (!self._index['Declarations'][node.attributes.referencedDeclaration]) { - self._index['Declarations'][node.attributes.referencedDeclaration] = [] + if (!this._index['Declarations'][node.attributes.referencedDeclaration]) { + this._index['Declarations'][node.attributes.referencedDeclaration] = [] } - self._index['Declarations'][node.attributes.referencedDeclaration].push(node) + this._index['Declarations'][node.attributes.referencedDeclaration].push(node) } - self._index['FlatReferences'][node.id] = node + this._index['FlatReferences'][node.id] = node return true } - for (var s in compilationResult.sources) { + for (const s in compilationResult.sources) { this.astWalker.walk(compilationResult.sources[s].legacyAST, callback) } } @@ -104,21 +102,19 @@ class ContextualListener { _highlight (node, compilationResult) { if (!node) return - var self = this - var position = this.sourceMappingDecoder.decode(node.src) - var eventId = this._highlightInternal(position, node) - let lastCompilationResult = self._deps.compilersArtefacts['__last'] + const position = this.sourceMappingDecoder.decode(node.src) + const eventId = this._highlightInternal(position, node) + let lastCompilationResult = this._deps.compilersArtefacts['__last'] if (eventId && lastCompilationResult) { this._activeHighlights.push({ eventId, position, fileTarget: lastCompilationResult.getSourceName(position.file), nodeId: node.id }) } } _highlightInternal (position, node) { - var self = this - let lastCompilationResult = self._deps.compilersArtefacts['__last'] + let lastCompilationResult = this._deps.compilersArtefacts['__last'] if (lastCompilationResult) { - var lineColumn = self._deps.offsetToLineColumnConverter.offsetToLineColumn(position, position.file, lastCompilationResult.getSourceCode().sources, lastCompilationResult.getAsts()) - var css = 'highlightreference' + let lineColumn = this._deps.offsetToLineColumnConverter.offsetToLineColumn(position, position.file, lastCompilationResult.getSourceCode().sources, lastCompilationResult.getAsts()) + let css = 'highlightreference' if (node.children && node.children.length) { // If node has children, highlight the entire line. if not, just highlight the current source position of the node. css = 'highlightreference' @@ -133,28 +129,27 @@ class ContextualListener { } } } - var fileName = lastCompilationResult.getSourceName(position.file) + const fileName = lastCompilationResult.getSourceName(position.file) if (fileName) { - return self.editor.addMarker(lineColumn, fileName, css) + return this.editor.addMarker(lineColumn, fileName, css) } } return null } _highlightExpressions (node, compilationResult) { - var self = this - function highlights (id) { - if (self._index['Declarations'] && self._index['Declarations'][id]) { - var refs = self._index['Declarations'][id] - for (var ref in refs) { - var node = refs[ref] - self._highlight(node, compilationResult) + const highlights = (id) => { + if (this._index['Declarations'] && this._index['Declarations'][id]) { + const refs = this._index['Declarations'][id] + for (const ref in refs) { + const node = refs[ref] + this._highlight(node, compilationResult) } } } if (node.attributes && node.attributes.referencedDeclaration) { highlights(node.attributes.referencedDeclaration) - var current = this._index['FlatReferences'][node.attributes.referencedDeclaration] + const current = this._index['FlatReferences'][node.attributes.referencedDeclaration] this._highlight(current, compilationResult) } else { highlights(node.id) @@ -164,23 +159,21 @@ class ContextualListener { } _stopHighlighting () { - var self = this - for (var eventKey in this._activeHighlights) { - var event = this._activeHighlights[eventKey] - self.editor.removeMarker(event.eventId, event.fileTarget) + for (const eventKey in this._activeHighlights) { + const event = this._activeHighlights[eventKey] + this.editor.removeMarker(event.eventId, event.fileTarget) } this._activeHighlights = [] } gasEstimation (node) { this._loadContractInfos(node) - var executionCost - var codeDepositCost + let executionCost, codeDepositCost if (node.name === 'FunctionDefinition') { - var visibility = node.attributes.visibility + const visibility = node.attributes.visibility if (!node.attributes.isConstructor) { - var fnName = node.attributes.name - var fn = fnName + this._getInputParams(node) + const fnName = node.attributes.name + const fn = fnName + this._getInputParams(node) if (visibility === 'public' || visibility === 'external') { executionCost = this.estimationObj.external[fn] } else if (visibility === 'private' || visibility === 'internal') { @@ -197,9 +190,9 @@ class ContextualListener { } _loadContractInfos (node) { - for (var i in this.nodes) { + for (const i in this.nodes) { if (this.nodes[i].id === node.attributes.scope) { - var contract = this.nodes[i] + const contract = this.nodes[i] this.contract = this.results.data.contracts[this.results.source.target][contract.attributes.name] this.estimationObj = this.contract.evm.gasEstimates this.creationCost = this.estimationObj.creation.totalCost @@ -209,16 +202,17 @@ class ContextualListener { } _getInputParams (node) { - var params = [] - for (var i in node.children) { + const params = [] + let target + for (const i in node.children) { if (node.children[i].name === 'ParameterList') { - var target = node.children[i] + target = node.children[i] break } } if (target) { - var children = target.children - for (var j in children) { + const children = target.children + for (const j in children) { if (children[j].name === 'VariableDeclaration') { params.push(children[j].attributes.type) } diff --git a/src/app/editor/editor.js b/src/app/editor/editor.js index bdb9734a2a..7fb63b24ec 100644 --- a/src/app/editor/editor.js +++ b/src/app/editor/editor.js @@ -1,24 +1,24 @@ 'use strict' -var EventManager = require('../../lib/events') -var yo = require('yo-yo') -var csjs = require('csjs-inject') -var ace = require('brace') +const EventManager = require('../../lib/events') +const yo = require('yo-yo') +const csjs = require('csjs-inject') +const ace = require('brace') require('brace/theme/tomorrow_night_blue') -var globalRegistry = require('../../global/registry') +const globalRegistry = require('../../global/registry') const SourceHighlighters = require('./SourceHighlighters') -var Range = ace.acequire('ace/range').Range +const Range = ace.acequire('ace/range').Range require('brace/ext/language_tools') require('brace/ext/searchbox') -var langTools = ace.acequire('ace/ext/language_tools') +const langTools = ace.acequire('ace/ext/language_tools') require('ace-mode-solidity/build/remix-ide/mode-solidity') require('brace/mode/javascript') require('brace/mode/python') require('brace/mode/json') -var styleGuide = require('../ui/styles-guide/theme-chooser') -var styles = styleGuide.chooser() +const styleGuide = require('../ui/styles-guide/theme-chooser') +const styles = styleGuide.chooser() function setTheme (cb) { if (styles.appProperties.aceTheme) { @@ -30,7 +30,7 @@ setTheme((path, theme) => { require('brace/theme/tomorrow_night_blue') }) -var css = csjs` +const css = csjs` .ace-editor { background-color : ${styles.editor.backgroundColor_Editor}; width : 100%; @@ -49,113 +49,176 @@ document.head.appendChild(yo` .highlightreference { position:absolute; z-index:20; - background-color: ${styles.editor.backgroundColor_Editor_Context_Highlights}; + background-color: ${ + styles.editor.backgroundColor_Editor_Context_Highlights + }; opacity: 0.7 } .highlightreferenceline { position:absolute; z-index:20; - background-color: ${styles.editor.backgroundColor_Editor_Context_Highlights}; + background-color: ${ + styles.editor.backgroundColor_Editor_Context_Highlights + }; opacity: 0.7 } .highlightcode { position:absolute; z-index:20; - background-color: ${styles.editor.backgroundColor_Editor_Context_Error_Highlights}; + background-color: ${ + styles.editor.backgroundColor_Editor_Context_Error_Highlights + }; } `) -function Editor (opts = {}, localRegistry) { - var self = this - var el = yo`
` - var editor = ace.edit(el) - if (styles.appProperties.aceTheme) { - editor.setTheme('ace/theme/' + styles.appProperties.aceTheme) - } - self._components = {} - self._components.registry = localRegistry || globalRegistry - self._deps = { - fileManager: self._components.registry.get('filemanager').api, - config: self._components.registry.get('config').api - } +class Editor { - ace.acequire('ace/ext/language_tools') - editor.setOptions({ - enableBasicAutocompletion: true, - enableLiveAutocompletion: true - }) - var flowCompleter = { - getCompletions: function (editor, session, pos, prefix, callback) { - // @TODO add here other propositions + constructor (opts = {}, localRegistry) { + // Dependancies + this._components = {} + this._components.registry = localRegistry || globalRegistry + this._deps = { + fileManager: this._components.registry.get('filemanager').api, + config: this._components.registry.get('config').api } - } - langTools.addCompleter(flowCompleter) - el.className += ' ' + css['ace-editor'] - el.editor = editor // required to access the editor during tests - self.render = function () { return el } - var event = new EventManager() - self.event = event - var sessions = {} - var sourceAnnotations = [] - var readOnlySessions = {} - var currentSession - - var emptySession = createSession('') - var modes = { - 'sol': 'ace/mode/solidity', - 'js': 'ace/mode/javascript', - 'py': 'ace/mode/python', - 'vy': 'ace/mode/python', - 'txt': 'ace/mode/text', - 'json': 'ace/mode/json', - 'abi': 'ace/mode/json' - } - editor.on('guttermousedown', function (e) { - var target = e.domEvent.target - if (target.className.indexOf('ace_gutter-cell') === -1) { - return + // Init + this.event = new EventManager() + this.sessions = {} + this.sourceAnnotations = [] + this.readOnlySessions = {} + this.previousInput = '' + this.saveTimeout = null + this.sourceHighlighters = new SourceHighlighters() + this.emptySession = this._createSession('') + this.modes = { + sol: 'ace/mode/solidity', + js: 'ace/mode/javascript', + py: 'ace/mode/python', + vy: 'ace/mode/python', + txt: 'ace/mode/text', + json: 'ace/mode/json', + abi: 'ace/mode/json' } - var row = e.getDocumentPosition().row - var breakpoints = e.editor.session.getBreakpoints() - for (var k in breakpoints) { - if (k === row.toString()) { - event.trigger('breakpointCleared', [currentSession, row]) - e.editor.session.clearBreakpoint(row) - e.stop() - return + + // Editor Setup + const el = yo`
` + this.editor = ace.edit(el) + ace.acequire('ace/ext/language_tools') + + // Unmap ctrl-t & ctrl-f + this.editor.commands.bindKeys({ 'ctrl-t': null }) + this.editor.setShowPrintMargin(false) + this.editor.resize(true) + + if (styles.appProperties.aceTheme) { + this.editor.setTheme('ace/theme/' + styles.appProperties.aceTheme) + } + + this.editor.setOptions({ + enableBasicAutocompletion: true, + enableLiveAutocompletion: true + }) + + el.className += ' ' + css['ace-editor'] + el.editor = this.editor // required to access the editor during tests + this.render = () => el + + // Completer for editor + const flowCompleter = { + getCompletions: (editor, session, pos, prefix, callback) => { + // @TODO add here other propositions } } - self.setBreakpoint(row) - event.trigger('breakpointAdded', [currentSession, row]) - e.stop() - }) - - this.displayEmptyReadOnlySession = function () { - currentSession = null - editor.setSession(emptySession) - editor.setReadOnly(true) + langTools.addCompleter(flowCompleter) + + // EVENTS LISTENERS + + // Gutter Mouse down + this.editor.on('guttermousedown', e => { + const target = e.domEvent.target + if (target.className.indexOf('ace_gutter-cell') === -1) { + return + } + const row = e.getDocumentPosition().row + const breakpoints = e.editor.session.getBreakpoints() + for (const k in breakpoints) { + if (k === row.toString()) { + this.event.trigger('breakpointCleared', [this.currentSession, row]) + e.editor.session.clearBreakpoint(row) + e.stop() + return + } + } + this.setBreakpoint(row) + this.event.trigger('breakpointAdded', [this.currentSession, row]) + e.stop() + }) + + // Do setup on initialisation here + this.editor.on('changeSession', () => { + this._onChange() + this.event.trigger('sessionSwitched', []) + + this.editor.getSession().on('change', () => { + this._onChange() + this.event.trigger('contentChanged', []) + }) + }) } - this.setBreakpoint = function (row, css) { - editor.session.setBreakpoint(row, css) + _onChange () { + const currentFile = this._deps.config.get('currentFile') + if (!currentFile) { + return + } + const input = this.get(currentFile) + if (!input) { + return + } + // if there's no change, don't do anything + if (input === this.previousInput) { + return + } + this.previousInput = input + + // fire storage update + // NOTE: save at most once per 5 seconds + if (this.saveTimeout) { + window.clearTimeout(this.saveTimeout) + } + this.saveTimeout = window.setTimeout(() => { + this._deps.fileManager.saveCurrentFile() + }, 5000) } - this.editorFontSize = function (incr) { - editor.setFontSize(editor.getFontSize() + incr) + _switchSession (path) { + this.currentSession = path + this.editor.setSession(this.sessions[this.currentSession]) + this.editor.setReadOnly(this.readOnlySessions[this.currentSession]) + this.editor.focus() } - this.setText = function (text) { - if (currentSession && sessions[currentSession]) { - sessions[currentSession].setValue(text) - } + /** + * Get Ace mode base of the extension of the session file + * @param {string} path Path of the file + */ + _getMode (path) { + let ext = path.indexOf('.') !== -1 ? /[^.]+$/.exec(path) : null + if (ext) ext = ext[0] + return ext && this.modes[ext] ? this.modes[ext] : this.modes['txt'] } - function createSession (content, mode) { - var s = new ace.EditSession(content) + /** + * Create an Ace session + * @param {string} content Content of the file to open + * @param {string} mode Ace Mode for this file [Default is `text`] + */ + _createSession (content, mode) { + const s = new ace.EditSession(content) s.setMode(mode || 'ace/mode/text') s.setUndoManager(new ace.UndoManager()) s.setTabSize(4) @@ -163,98 +226,156 @@ function Editor (opts = {}, localRegistry) { return s } - function switchSession (path) { - currentSession = path - editor.setSession(sessions[currentSession]) - editor.setReadOnly(readOnlySessions[currentSession]) - editor.focus() + /** + * Attempts to find the string in the current document + * @param {string} string + */ + find (string) { + return this.editor.find(string) } - function getMode (path) { - var ext = path.indexOf('.') !== -1 ? /[^.]+$/.exec(path) : null - if (ext) ext = ext[0] - return ext && modes[ext] ? modes[ext] : modes['txt'] + /** + * Display an Empty read-only session + */ + displayEmptyReadOnlySession () { + this.currentSession = null + this.editor.setSession(this.emptySession) + this.editor.setReadOnly(true) + } + + /** + * Sets a breakpoint on the row number + * @param {number} row Line index of the breakpoint + * @param {string} className Class of the breakpoint + */ + setBreakpoint (row, className) { + this.editor.session.setBreakpoint(row, className) + } + + /** + * Increment the font size (in pixels) for the editor text. + * @param {number} incr The amount of pixels to add to the font. + */ + editorFontSize (incr) { + this.editor.setFontSize(this.editor.getFontSize() + incr) } - this.open = function (path, content) { - if (!sessions[path]) { - var session = createSession(content, getMode(path)) - sessions[path] = session - readOnlySessions[path] = false - } else if (sessions[path].getValue() !== content) { - sessions[path].setValue(content) + /** + * Set the text in the current session, if any. + * @param {string} text New text to be place. + */ + setText (text) { + if (this.currentSession && this.sessions[this.currentSession]) { + this.sessions[this.currentSession].setValue(text) } - switchSession(path) } - this.openReadOnly = function (path, content) { - if (!sessions[path]) { - var session = createSession(content, getMode(path)) - sessions[path] = session - readOnlySessions[path] = true + /** + * Upsert and open a session. + * @param {string} path Path of the session to open. + * @param {string} content Content of the document or update. + */ + open (path, content) { + if (!this.sessions[path]) { + const session = this._createSession(content, this._getMode(path)) + this.sessions[path] = session + this.readOnlySessions[path] = false + } else if (this.sessions[path].getValue() !== content) { + this.sessions[path].setValue(content) } - switchSession(path) + this._switchSession(path) } /** - * returns the content of the current session - * - * @return {String} content of the file referenced by @arg path - */ - this.currentContent = function () { + * Upsert and Open a session and set it as Read-only. + * @param {string} path Path of the session to open. + * @param {string} content Content of the document or update. + */ + openReadOnly (path, content) { + if (!this.sessions[path]) { + const session = this._createSession(content, this._getMode(path)) + this.sessions[path] = session + this.readOnlySessions[path] = true + } + this._switchSession(path) + } + + /** + * Content of the current session + * @return {String} content of the file referenced by @arg path + */ + currentContent () { return this.get(this.current()) } /** - * returns the content of the session targeted by @arg path - * if @arg path is null, the content of the current session is returned - * - * @return {String} content of the file referenced by @arg path - */ - this.get = function (path) { - if (!path || currentSession === path) { - return editor.getValue() - } else if (sessions[path]) { - return sessions[path].getValue() + * Content of the session targeted by @arg path + * if @arg path is null, the content of the current session is returned + * @param {string} path Path of the session to get. + * @return {String} content of the file referenced by @arg path + */ + get (path) { + if (!path || this.currentSession === path) { + return this.editor.getValue() + } else if (this.sessions[path]) { + return this.sessions[path].getValue() } } /** - * returns the path of the currently editing file - * returns `undefined` if no session is being editer - * - * @return {String} path of the current session - */ - this.current = function () { - if (editor.getSession() === emptySession) { + * Path of the currently editing file + * returns `undefined` if no session is being editer + * @return {String} path of the current session + */ + current () { + if (this.editor.getSession() === this.emptySession) { return } - return currentSession + return this.currentSession } - this.getCursorPosition = function () { - return editor.session.doc.positionToIndex(editor.getCursorPosition(), 0) + /** + * The position of the cursor + */ + getCursorPosition () { + return this.editor.session.doc.positionToIndex( + this.editor.getCursorPosition(), + 0 + ) } - this.discardCurrentSession = function () { - if (sessions[currentSession]) { - delete sessions[currentSession] - currentSession = null + /** + * Remove the current session from the list of sessions. + */ + discardCurrentSession () { + if (this.sessions[this.currentSession]) { + delete this.sessions[this.currentSession] + this.currentSession = null } } - this.discard = function (path) { - if (sessions[path]) delete sessions[path] - if (currentSession === path) currentSession = null + /** + * Remove a session based on its path. + * @param {string} path + */ + discard (path) { + if (this.sessions[path]) delete this.sessions[path] + if (this.currentSession === path) this.currentSession = null } - this.resize = function (useWrapMode) { - editor.resize() - var session = editor.getSession() + /** + * Resize the editor, and sets whether or not line wrapping is enabled. + * @param {boolean} useWrapMode Enable (or disable) wrap mode + */ + resize (useWrapMode) { + this.editor.resize() + const session = this.editor.getSession() session.setUseWrapMode(useWrapMode) if (session.getUseWrapMode()) { - var characterWidth = editor.renderer.characterWidth - var contentWidth = editor.container.ownerDocument.getElementsByClassName('ace_scroller')[0].clientWidth + const characterWidth = this.editor.renderer.characterWidth + const contentWidth = this.editor.container.ownerDocument.getElementsByClassName( + 'ace_scroller' + )[0].clientWidth if (contentWidth > 0) { session.setWrapLimit(parseInt(contentWidth / characterWidth, 10)) @@ -262,89 +383,81 @@ function Editor (opts = {}, localRegistry) { } } - this.addMarker = function (lineColumnPos, source, cssClass) { - var currentRange = new Range(lineColumnPos.start.line, lineColumnPos.start.column, lineColumnPos.end.line, lineColumnPos.end.column) - if (sessions[source]) { - return sessions[source].addMarker(currentRange, cssClass) + /** + * Adds a new marker to the given `Range`. + * @param {*} lineColumnPos + * @param {string} source Path of the session to add the mark on. + * @param {string} cssClass css to apply to the mark. + */ + addMarker (lineColumnPos, source, cssClass) { + const currentRange = new Range( + lineColumnPos.start.line, + lineColumnPos.start.column, + lineColumnPos.end.line, + lineColumnPos.end.column + ) + if (this.sessions[source]) { + return this.sessions[source].addMarker(currentRange, cssClass) } return null } - this.scrollToLine = function (line, center, animate, callback) { - editor.scrollToLine(line, center, animate, callback) + /** + * Scrolls to a line. If center is true, it puts the line in middle of screen (or attempts to). + * @param {number} line The line to scroll to + * @param {boolean} center If true + * @param {boolean} animate If true animates scrolling + * @param {Function} callback Function to be called when the animation has finished + */ + scrollToLine (line, center, animate, callback) { + this.editor.scrollToLine(line, center, animate, callback) } - this.removeMarker = function (markerId, source) { - if (sessions[source]) { - sessions[source].removeMarker(markerId) + /** + * Remove a marker from the session + * @param {string} markerId Id of the marker + * @param {string} source Path of the session + */ + removeMarker (markerId, source) { + if (this.sessions[source]) { + this.sessions[source].removeMarker(markerId) } } - this.clearAnnotations = function () { - sourceAnnotations = [] - editor.getSession().clearAnnotations() - } - - this.addAnnotation = function (annotation) { - sourceAnnotations[sourceAnnotations.length] = annotation - this.setAnnotations(sourceAnnotations) - } - - this.setAnnotations = function (sourceAnnotations) { - editor.getSession().setAnnotations(sourceAnnotations) + /** + * Clears all the annotations for the current session. + */ + clearAnnotations () { + this.sourceAnnotations = [] + this.editor.getSession().clearAnnotations() } - this.gotoLine = function (line, col) { - editor.focus() - editor.gotoLine(line + 1, col - 1, true) + /** + * Add an annotation to the current session. + * @param {Object} annotation + */ + addAnnotation (annotation) { + this.sourceAnnotations[this.sourceAnnotations.length] = annotation + this.setAnnotations(this.sourceAnnotations) } - this.find = (string) => editor.find(string) - - this.previousInput = '' - this.saveTimeout = null - // Do setup on initialisation here - editor.on('changeSession', function () { - editorOnChange(self) - event.trigger('sessionSwitched', []) - - editor.getSession().on('change', function () { - editorOnChange(self) - event.trigger('contentChanged', []) - }) - }) - - // Unmap ctrl-t & ctrl-f - editor.commands.bindKeys({ 'ctrl-t': null }) - editor.setShowPrintMargin(false) - editor.resize(true) - - this.sourceHighlighters = new SourceHighlighters() -} - -function editorOnChange (self) { - var currentFile = self._deps.config.get('currentFile') - if (!currentFile) { - return - } - var input = self.get(currentFile) - if (!input) { - return - } - // if there's no change, don't do anything - if (input === self.previousInput) { - return + /** + * Set a list of annotations to the current session. + * @param {Array} annotation + */ + setAnnotations (sourceAnnotations) { + this.editor.getSession().setAnnotations(sourceAnnotations) } - self.previousInput = input - // fire storage update - // NOTE: save at most once per 5 seconds - if (self.saveTimeout) { - window.clearTimeout(self.saveTimeout) + /** + * Moves the cursor and focus to the specified line and column number + * @param {number} line + * @param {number} col + */ + gotoLine (line, col) { + this.editor.focus() + this.editor.gotoLine(line + 1, col - 1, true) } - self.saveTimeout = window.setTimeout(() => { - self._deps.fileManager.saveCurrentFile() - }, 5000) } module.exports = Editor diff --git a/src/app/editor/sourceHighlighter.js b/src/app/editor/sourceHighlighter.js index d99f469f3e..44455d49af 100644 --- a/src/app/editor/sourceHighlighter.js +++ b/src/app/editor/sourceHighlighter.js @@ -1,20 +1,19 @@ 'use strict' -var csjs = require('csjs-inject') -var globlalRegistry = require('../../global/registry') -var styleGuide = require('../ui/styles-guide/theme-chooser') -var styles = styleGuide.chooser() +const csjs = require('csjs-inject') +const globlalRegistry = require('../../global/registry') +const styleGuide = require('../ui/styles-guide/theme-chooser') +const styles = styleGuide.chooser() class SourceHighlighter { constructor (localRegistry) { - const self = this - self._components = {} - self._components.registry = localRegistry || globlalRegistry + this._components = {} + this._components.registry = localRegistry || globlalRegistry // dependencies - self._deps = { - editor: self._components.registry.get('editor').api, - config: self._components.registry.get('config').api, - fileManager: self._components.registry.get('filemanager').api, - compilerArtefacts: self._components.registry.get('compilersartefacts').api + this._deps = { + editor: this._components.registry.get('editor').api, + config: this._components.registry.get('config').api, + fileManager: this._components.registry.get('filemanager').api, + compilerArtefacts: this._components.registry.get('compilersartefacts').api } this.statementMarker = null this.fullLineMarker = null @@ -26,7 +25,7 @@ class SourceHighlighter { if (this.fullLineMarker) this._deps.editor.removeMarker(this.fullLineMarker, this.source) let lastCompilationResult = this._deps.compilerArtefacts['__last'] if (location && location.file !== undefined && lastCompilationResult) { - var path = lastCompilationResult.getSourceName(location.file) + const path = lastCompilationResult.getSourceName(location.file) if (path) { this.currentSourceLocationFromfileName(lineColumnPos, path) } @@ -45,7 +44,7 @@ class SourceHighlighter { this._deps.fileManager.switchFile(this.source) } - var css = csjs` + const css = csjs` .highlightcode { position:absolute; z-index:20;