Merge pull request #1687 from ethereum/swap_it_main_view

Refactor Editor to ES6
pull/1/head
yann300 6 years ago committed by GitHub
commit 2442e353c9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 8
      src/app/editor/SourceHighlighters.js
  2. 104
      src/app/editor/contextView.js
  3. 116
      src/app/editor/contextualListener.js
  4. 541
      src/app/editor/editor.js
  5. 27
      src/app/editor/sourceHighlighter.js

@ -1,7 +1,7 @@
'use strict' 'use strict'
var SourceHighlighter = require('./sourceHighlighter') const SourceHighlighter = require('./sourceHighlighter')
module.exports = class SourceHighlighters { class SourceHighlighters {
constructor () { constructor () {
this.highlighters = {} this.highlighters = {}
@ -17,7 +17,7 @@ module.exports = class SourceHighlighters {
// TODO what to do with mod? // TODO what to do with mod?
highlight (mod, lineColumnPos, filePath, hexColor, cb) { highlight (mod, lineColumnPos, filePath, hexColor, cb) {
var position let position
try { try {
position = JSON.parse(lineColumnPos) position = JSON.parse(lineColumnPos)
} catch (e) { } catch (e) {
@ -34,3 +34,5 @@ module.exports = class SourceHighlighters {
cb() cb()
} }
} }
module.exports = SourceHighlighters

@ -1,10 +1,10 @@
'use strict' 'use strict'
var yo = require('yo-yo') const yo = require('yo-yo')
var remixLib = require('remix-lib') const remixLib = require('remix-lib')
var SourceMappingDecoder = remixLib.SourceMappingDecoder const SourceMappingDecoder = remixLib.SourceMappingDecoder
var globalRegistry = require('../../global/registry') const globalRegistry = require('../../global/registry')
var css = require('./styles/contextView-styles') const css = require('./styles/contextView-styles')
/* /*
Display information about the current focused code: Display information about the current focused code:
@ -15,30 +15,29 @@ var css = require('./styles/contextView-styles')
*/ */
class ContextView { class ContextView {
constructor (opts, localRegistry) { constructor (opts, localRegistry) {
const self = this this._components = {}
self._components = {} this._components.registry = localRegistry || globalRegistry
self._components.registry = localRegistry || globalRegistry this.contextualListener = opts.contextualListener
self.contextualListener = opts.contextualListener this.editor = opts.editor
self.editor = opts.editor this._deps = {
self._deps = { compilersArtefacts: this._components.registry.get('compilersartefacts').api,
compilersArtefacts: self._components.registry.get('compilersartefacts').api, offsetToLineColumnConverter: this._components.registry.get('offsettolinecolumnconverter').api,
offsetToLineColumnConverter: self._components.registry.get('offsettolinecolumnconverter').api, config: this._components.registry.get('config').api,
config: self._components.registry.get('config').api, fileManager: this._components.registry.get('filemanager').api
fileManager: self._components.registry.get('filemanager').api
} }
this._view this._view
this._nodes this._nodes
this._current this._current
this.sourceMappingDecoder = new SourceMappingDecoder() this.sourceMappingDecoder = new SourceMappingDecoder()
this.previousElement = null this.previousElement = null
self.contextualListener.event.register('contextChanged', nodes => { this.contextualListener.event.register('contextChanged', nodes => {
this._nodes = nodes this._nodes = nodes
this.update() this.update()
}) })
} }
render () { render () {
var view = yo`<div class=${css.contextview}> const view = yo`<div class=${css.contextview}>
<div class=${css.container}> <div class=${css.container}>
${this._renderTarget()} ${this._renderTarget()}
</div> </div>
@ -70,13 +69,14 @@ class ContextView {
} }
_renderTarget () { _renderTarget () {
var previous = this._current let last
const previous = this._current
if (this._nodes && this._nodes.length) { if (this._nodes && this._nodes.length) {
var last = this._nodes[this._nodes.length - 1] last = this._nodes[this._nodes.length - 1]
if (isDefinition(last)) { if (isDefinition(last)) {
this._current = last this._current = last
} else { } else {
var target = this.contextualListener.declarationOf(last) const target = this.contextualListener.declarationOf(last)
if (target) { if (target) {
this._current = target this._current = target
} else { } else {
@ -91,27 +91,26 @@ class ContextView {
} }
_jumpToInternal (position) { _jumpToInternal (position) {
var self = this const jumpToLine = (lineColumn) => {
function jumpToLine (lineColumn) {
if (lineColumn.start && lineColumn.start.line && lineColumn.start.column) { 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) { if (lastCompilationResult && lastCompilationResult.data) {
var lineColumn = self._deps.offsetToLineColumnConverter.offsetToLineColumn( const lineColumn = this._deps.offsetToLineColumnConverter.offsetToLineColumn(
position, position,
position.file, position.file,
lastCompilationResult.getSourceCode().sources, lastCompilationResult.getSourceCode().sources,
lastCompilationResult.getAsts()) lastCompilationResult.getAsts())
var filename = lastCompilationResult.getSourceName(position.file) const filename = lastCompilationResult.getSourceName(position.file)
// TODO: refactor with rendererAPI.errorClick // TODO: refactor with rendererAPI.errorClick
if (filename !== self._deps.config.get('currentFile')) { if (filename !== this._deps.config.get('currentFile')) {
var provider = self._deps.fileManager.fileProviderOf(filename) const provider = this._deps.fileManager.fileProviderOf(filename)
if (provider) { if (provider) {
provider.exists(filename, (error, exist) => { provider.exists(filename, (error, exist) => {
if (error) return console.log(error) if (error) return console.log(error)
self._deps.fileManager.switchFile(filename) this._deps.fileManager.switchFile(filename)
jumpToLine(lineColumn) jumpToLine(lineColumn)
}) })
} }
@ -123,14 +122,13 @@ class ContextView {
_render (node, nodeAtCursorPosition) { _render (node, nodeAtCursorPosition) {
if (!node) return yo`<div></div>` if (!node) return yo`<div></div>`
var self = this let references = this.contextualListener.referencesOf(node)
var references = self.contextualListener.referencesOf(node) const type = (node.attributes && node.attributes.type) ? node.attributes.type : node.name
var type = (node.attributes && node.attributes.type) ? node.attributes.type : node.name
references = `${references ? references.length : '0'} reference(s)` references = `${references ? references.length : '0'} reference(s)`
var ref = 0 let ref = 0
var nodes = self.contextualListener.getActiveHighlights() const nodes = this.contextualListener.getActiveHighlights()
for (var k in nodes) { for (const k in nodes) {
if (nodeAtCursorPosition.id === nodes[k].nodeId) { if (nodeAtCursorPosition.id === nodes[k].nodeId) {
ref = k ref = k
break break
@ -138,22 +136,35 @@ class ContextView {
} }
// JUMP BETWEEN REFERENCES // JUMP BETWEEN REFERENCES
function jump (e) { const jump = (e) => {
e.target.dataset.action === 'next' ? ref++ : ref-- e.target.dataset.action === 'next' ? ref++ : ref--
if (ref < 0) ref = nodes.length - 1 if (ref < 0) ref = nodes.length - 1
if (ref >= nodes.length) ref = 0 if (ref >= nodes.length) ref = 0
self._jumpToInternal(nodes[ref].position) this._jumpToInternal(nodes[ref].position)
} }
function jumpTo () { const jumpTo = () => {
if (node && node.src) { if (node && node.src) {
var position = self.sourceMappingDecoder.decode(node.src) const position = this.sourceMappingDecoder.decode(node.src)
if (position) { 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`<div class=${css.gasEstimation}>
<img class=${css.gasStationIcon} title='Gas estimation' src='assets/img/gasStation_50.png'>
${estimatedGas}
</div>`
}
}
return yo`<div class=${css.line}> return yo`<div class=${css.line}>
<div title=${type} class=${css.type}>${type}</div> <div title=${type} class=${css.type}>${type}</div>
<div title=${node.attributes.name} class=${css.name}>${node.attributes.name}</div> <div title=${node.attributes.name} class=${css.name}>${node.attributes.name}</div>
@ -163,19 +174,6 @@ class ContextView {
<i data-action='next' class="fa fa-chevron-down ${css.jump}" aria-hidden="true" onclick=${jump}></i> <i data-action='next' class="fa fa-chevron-down ${css.jump}" aria-hidden="true" onclick=${jump}></i>
${showGasEstimation()} ${showGasEstimation()}
</div>` </div>`
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`<div class=${css.gasEstimation}>
<img class=${css.gasStationIcon} title='Gas estimation' src='assets/img/gasStation_50.png'>
${estimatedGas}
</div>`
}
}
} }
} }

@ -1,25 +1,24 @@
'use strict' 'use strict'
var remixLib = require('remix-lib') const remixLib = require('remix-lib')
var SourceMappingDecoder = remixLib.SourceMappingDecoder const SourceMappingDecoder = remixLib.SourceMappingDecoder
var AstWalker = remixLib.AstWalker const AstWalker = remixLib.AstWalker
var EventManager = require('../../lib/events') const EventManager = require('../../lib/events')
var globalRegistry = require('../../global/registry') const globalRegistry = require('../../global/registry')
/* /*
trigger contextChanged(nodes) trigger contextChanged(nodes)
*/ */
class ContextualListener { class ContextualListener {
constructor (opts, localRegistry) { constructor (opts, localRegistry) {
var self = this
this.event = new EventManager() this.event = new EventManager()
self._components = {} this._components = {}
self._components.registry = localRegistry || globalRegistry this._components.registry = localRegistry || globalRegistry
self.editor = opts.editor this.editor = opts.editor
self.pluginManager = opts.pluginManager this.pluginManager = opts.pluginManager
self._deps = { this._deps = {
compilersArtefacts: self._components.registry.get('compilersartefacts').api, compilersArtefacts: this._components.registry.get('compilersartefacts').api,
config: self._components.registry.get('config').api, config: this._components.registry.get('config').api,
offsetToLineColumnConverter: self._components.registry.get('offsettolinecolumnconverter').api offsetToLineColumnConverter: this._components.registry.get('offsettolinecolumnconverter').api
} }
this._index = { this._index = {
Declarations: {}, Declarations: {},
@ -27,7 +26,7 @@ class ContextualListener {
} }
this._activeHighlights = [] this._activeHighlights = []
self.pluginManager.event.register('sendCompilationResult', (file, source, languageVersion, data) => { this.pluginManager.event.register('sendCompilationResult', (file, source, languageVersion, data) => {
this._stopHighlighting() this._stopHighlighting()
this._index = { this._index = {
Declarations: {}, Declarations: {},
@ -36,13 +35,13 @@ class ContextualListener {
this._buildIndex(data, source) this._buildIndex(data, source)
}) })
self.editor.event.register('contentChanged', () => { this._stopHighlighting() }) this.editor.event.register('contentChanged', () => { this._stopHighlighting() })
this.sourceMappingDecoder = new SourceMappingDecoder() this.sourceMappingDecoder = new SourceMappingDecoder()
this.astWalker = new AstWalker() this.astWalker = new AstWalker()
setInterval(() => { setInterval(() => {
if (self._deps.compilersArtefacts['__last']) { if (this._deps.compilersArtefacts['__last']) {
this._highlightItems(self.editor.getCursorPosition(), self._deps.compilersArtefacts['__last'], self._deps.config.get('currentFile')) this._highlightItems(this.editor.getCursorPosition(), this._deps.compilersArtefacts['__last'], this._deps.config.get('currentFile'))
} }
}, 1000) }, 1000)
} }
@ -73,7 +72,7 @@ class ContextualListener {
this.currentPosition = cursorPosition this.currentPosition = cursorPosition
this.currentFile = file this.currentFile = file
if (compilationResult && compilationResult.data && compilationResult.data.sources[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 this.nodes = nodes
if (nodes && nodes.length && nodes[nodes.length - 1]) { if (nodes && nodes.length && nodes[nodes.length - 1]) {
this._highlightExpressions(nodes[nodes.length - 1], compilationResult) this._highlightExpressions(nodes[nodes.length - 1], compilationResult)
@ -84,19 +83,18 @@ class ContextualListener {
_buildIndex (compilationResult, source) { _buildIndex (compilationResult, source) {
if (compilationResult && compilationResult.sources) { if (compilationResult && compilationResult.sources) {
var self = this const callback = {}
var callback = {} callback['*'] = (node) => {
callback['*'] = function (node) {
if (node && node.attributes && node.attributes.referencedDeclaration) { if (node && node.attributes && node.attributes.referencedDeclaration) {
if (!self._index['Declarations'][node.attributes.referencedDeclaration]) { if (!this._index['Declarations'][node.attributes.referencedDeclaration]) {
self._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 return true
} }
for (var s in compilationResult.sources) { for (const s in compilationResult.sources) {
this.astWalker.walk(compilationResult.sources[s].legacyAST, callback) this.astWalker.walk(compilationResult.sources[s].legacyAST, callback)
} }
} }
@ -104,21 +102,19 @@ class ContextualListener {
_highlight (node, compilationResult) { _highlight (node, compilationResult) {
if (!node) return if (!node) return
var self = this const position = this.sourceMappingDecoder.decode(node.src)
var position = this.sourceMappingDecoder.decode(node.src) const eventId = this._highlightInternal(position, node)
var eventId = this._highlightInternal(position, node) let lastCompilationResult = this._deps.compilersArtefacts['__last']
let lastCompilationResult = self._deps.compilersArtefacts['__last']
if (eventId && lastCompilationResult) { if (eventId && lastCompilationResult) {
this._activeHighlights.push({ eventId, position, fileTarget: lastCompilationResult.getSourceName(position.file), nodeId: node.id }) this._activeHighlights.push({ eventId, position, fileTarget: lastCompilationResult.getSourceName(position.file), nodeId: node.id })
} }
} }
_highlightInternal (position, node) { _highlightInternal (position, node) {
var self = this let lastCompilationResult = this._deps.compilersArtefacts['__last']
let lastCompilationResult = self._deps.compilersArtefacts['__last']
if (lastCompilationResult) { if (lastCompilationResult) {
var lineColumn = self._deps.offsetToLineColumnConverter.offsetToLineColumn(position, position.file, lastCompilationResult.getSourceCode().sources, lastCompilationResult.getAsts()) let lineColumn = this._deps.offsetToLineColumnConverter.offsetToLineColumn(position, position.file, lastCompilationResult.getSourceCode().sources, lastCompilationResult.getAsts())
var css = 'highlightreference' let css = 'highlightreference'
if (node.children && node.children.length) { 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. // If node has children, highlight the entire line. if not, just highlight the current source position of the node.
css = 'highlightreference' css = 'highlightreference'
@ -133,28 +129,27 @@ class ContextualListener {
} }
} }
} }
var fileName = lastCompilationResult.getSourceName(position.file) const fileName = lastCompilationResult.getSourceName(position.file)
if (fileName) { if (fileName) {
return self.editor.addMarker(lineColumn, fileName, css) return this.editor.addMarker(lineColumn, fileName, css)
} }
} }
return null return null
} }
_highlightExpressions (node, compilationResult) { _highlightExpressions (node, compilationResult) {
var self = this const highlights = (id) => {
function highlights (id) { if (this._index['Declarations'] && this._index['Declarations'][id]) {
if (self._index['Declarations'] && self._index['Declarations'][id]) { const refs = this._index['Declarations'][id]
var refs = self._index['Declarations'][id] for (const ref in refs) {
for (var ref in refs) { const node = refs[ref]
var node = refs[ref] this._highlight(node, compilationResult)
self._highlight(node, compilationResult)
} }
} }
} }
if (node.attributes && node.attributes.referencedDeclaration) { if (node.attributes && node.attributes.referencedDeclaration) {
highlights(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) this._highlight(current, compilationResult)
} else { } else {
highlights(node.id) highlights(node.id)
@ -164,23 +159,21 @@ class ContextualListener {
} }
_stopHighlighting () { _stopHighlighting () {
var self = this for (const eventKey in this._activeHighlights) {
for (var eventKey in this._activeHighlights) { const event = this._activeHighlights[eventKey]
var event = this._activeHighlights[eventKey] this.editor.removeMarker(event.eventId, event.fileTarget)
self.editor.removeMarker(event.eventId, event.fileTarget)
} }
this._activeHighlights = [] this._activeHighlights = []
} }
gasEstimation (node) { gasEstimation (node) {
this._loadContractInfos(node) this._loadContractInfos(node)
var executionCost let executionCost, codeDepositCost
var codeDepositCost
if (node.name === 'FunctionDefinition') { if (node.name === 'FunctionDefinition') {
var visibility = node.attributes.visibility const visibility = node.attributes.visibility
if (!node.attributes.isConstructor) { if (!node.attributes.isConstructor) {
var fnName = node.attributes.name const fnName = node.attributes.name
var fn = fnName + this._getInputParams(node) const fn = fnName + this._getInputParams(node)
if (visibility === 'public' || visibility === 'external') { if (visibility === 'public' || visibility === 'external') {
executionCost = this.estimationObj.external[fn] executionCost = this.estimationObj.external[fn]
} else if (visibility === 'private' || visibility === 'internal') { } else if (visibility === 'private' || visibility === 'internal') {
@ -197,9 +190,9 @@ class ContextualListener {
} }
_loadContractInfos (node) { _loadContractInfos (node) {
for (var i in this.nodes) { for (const i in this.nodes) {
if (this.nodes[i].id === node.attributes.scope) { 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.contract = this.results.data.contracts[this.results.source.target][contract.attributes.name]
this.estimationObj = this.contract.evm.gasEstimates this.estimationObj = this.contract.evm.gasEstimates
this.creationCost = this.estimationObj.creation.totalCost this.creationCost = this.estimationObj.creation.totalCost
@ -209,16 +202,17 @@ class ContextualListener {
} }
_getInputParams (node) { _getInputParams (node) {
var params = [] const params = []
for (var i in node.children) { let target
for (const i in node.children) {
if (node.children[i].name === 'ParameterList') { if (node.children[i].name === 'ParameterList') {
var target = node.children[i] target = node.children[i]
break break
} }
} }
if (target) { if (target) {
var children = target.children const children = target.children
for (var j in children) { for (const j in children) {
if (children[j].name === 'VariableDeclaration') { if (children[j].name === 'VariableDeclaration') {
params.push(children[j].attributes.type) params.push(children[j].attributes.type)
} }

@ -1,24 +1,24 @@
'use strict' 'use strict'
var EventManager = require('../../lib/events') const EventManager = require('../../lib/events')
var yo = require('yo-yo') const yo = require('yo-yo')
var csjs = require('csjs-inject') const csjs = require('csjs-inject')
var ace = require('brace') const ace = require('brace')
require('brace/theme/tomorrow_night_blue') require('brace/theme/tomorrow_night_blue')
var globalRegistry = require('../../global/registry') const globalRegistry = require('../../global/registry')
const SourceHighlighters = require('./SourceHighlighters') 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/language_tools')
require('brace/ext/searchbox') 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('ace-mode-solidity/build/remix-ide/mode-solidity')
require('brace/mode/javascript') require('brace/mode/javascript')
require('brace/mode/python') require('brace/mode/python')
require('brace/mode/json') require('brace/mode/json')
var styleGuide = require('../ui/styles-guide/theme-chooser') const styleGuide = require('../ui/styles-guide/theme-chooser')
var styles = styleGuide.chooser() const styles = styleGuide.chooser()
function setTheme (cb) { function setTheme (cb) {
if (styles.appProperties.aceTheme) { if (styles.appProperties.aceTheme) {
@ -30,7 +30,7 @@ setTheme((path, theme) => {
require('brace/theme/tomorrow_night_blue') require('brace/theme/tomorrow_night_blue')
}) })
var css = csjs` const css = csjs`
.ace-editor { .ace-editor {
background-color : ${styles.editor.backgroundColor_Editor}; background-color : ${styles.editor.backgroundColor_Editor};
width : 100%; width : 100%;
@ -49,113 +49,176 @@ document.head.appendChild(yo`
.highlightreference { .highlightreference {
position:absolute; position:absolute;
z-index:20; z-index:20;
background-color: ${styles.editor.backgroundColor_Editor_Context_Highlights}; background-color: ${
styles.editor.backgroundColor_Editor_Context_Highlights
};
opacity: 0.7 opacity: 0.7
} }
.highlightreferenceline { .highlightreferenceline {
position:absolute; position:absolute;
z-index:20; z-index:20;
background-color: ${styles.editor.backgroundColor_Editor_Context_Highlights}; background-color: ${
styles.editor.backgroundColor_Editor_Context_Highlights
};
opacity: 0.7 opacity: 0.7
} }
.highlightcode { .highlightcode {
position:absolute; position:absolute;
z-index:20; z-index:20;
background-color: ${styles.editor.backgroundColor_Editor_Context_Error_Highlights}; background-color: ${
styles.editor.backgroundColor_Editor_Context_Error_Highlights
};
} }
</style> </style>
`) `)
function Editor (opts = {}, localRegistry) { class Editor {
var self = this
var el = yo`<div id="input"></div>`
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
}
ace.acequire('ace/ext/language_tools') constructor (opts = {}, localRegistry) {
editor.setOptions({ // Dependancies
enableBasicAutocompletion: true, this._components = {}
enableLiveAutocompletion: true this._components.registry = localRegistry || globalRegistry
}) this._deps = {
var flowCompleter = { fileManager: this._components.registry.get('filemanager').api,
getCompletions: function (editor, session, pos, prefix, callback) { config: this._components.registry.get('config').api
// @TODO add here other propositions
} }
}
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) { // Init
var target = e.domEvent.target this.event = new EventManager()
if (target.className.indexOf('ace_gutter-cell') === -1) { this.sessions = {}
return 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() // Editor Setup
for (var k in breakpoints) { const el = yo`<div id="input"></div>`
if (k === row.toString()) { this.editor = ace.edit(el)
event.trigger('breakpointCleared', [currentSession, row]) ace.acequire('ace/ext/language_tools')
e.editor.session.clearBreakpoint(row)
e.stop() // Unmap ctrl-t & ctrl-f
return 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) langTools.addCompleter(flowCompleter)
event.trigger('breakpointAdded', [currentSession, row])
e.stop() // EVENTS LISTENERS
})
// Gutter Mouse down
this.displayEmptyReadOnlySession = function () { this.editor.on('guttermousedown', e => {
currentSession = null const target = e.domEvent.target
editor.setSession(emptySession) if (target.className.indexOf('ace_gutter-cell') === -1) {
editor.setReadOnly(true) 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) { _onChange () {
editor.session.setBreakpoint(row, css) 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) { _switchSession (path) {
editor.setFontSize(editor.getFontSize() + incr) 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]) { * Get Ace mode base of the extension of the session file
sessions[currentSession].setValue(text) * @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.setMode(mode || 'ace/mode/text')
s.setUndoManager(new ace.UndoManager()) s.setUndoManager(new ace.UndoManager())
s.setTabSize(4) s.setTabSize(4)
@ -163,98 +226,156 @@ function Editor (opts = {}, localRegistry) {
return s return s
} }
function switchSession (path) { /**
currentSession = path * Attempts to find the string in the current document
editor.setSession(sessions[currentSession]) * @param {string} string
editor.setReadOnly(readOnlySessions[currentSession]) */
editor.focus() find (string) {
return this.editor.find(string)
} }
function getMode (path) { /**
var ext = path.indexOf('.') !== -1 ? /[^.]+$/.exec(path) : null * Display an Empty read-only session
if (ext) ext = ext[0] */
return ext && modes[ext] ? modes[ext] : modes['txt'] 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]) { * Set the text in the current session, if any.
var session = createSession(content, getMode(path)) * @param {string} text New text to be place.
sessions[path] = session */
readOnlySessions[path] = false setText (text) {
} else if (sessions[path].getValue() !== content) { if (this.currentSession && this.sessions[this.currentSession]) {
sessions[path].setValue(content) this.sessions[this.currentSession].setValue(text)
} }
switchSession(path)
} }
this.openReadOnly = function (path, content) { /**
if (!sessions[path]) { * Upsert and open a session.
var session = createSession(content, getMode(path)) * @param {string} path Path of the session to open.
sessions[path] = session * @param {string} content Content of the document or update.
readOnlySessions[path] = true */
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 * Upsert and Open a session and set it as Read-only.
* * @param {string} path Path of the session to open.
* @return {String} content of the file referenced by @arg path * @param {string} content Content of the document or update.
*/ */
this.currentContent = function () { 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()) return this.get(this.current())
} }
/** /**
* returns the content of the session targeted by @arg path * Content of the session targeted by @arg path
* if @arg path is null, the content of the current session is returned * 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 * @return {String} content of the file referenced by @arg path
*/ */
this.get = function (path) { get (path) {
if (!path || currentSession === path) { if (!path || this.currentSession === path) {
return editor.getValue() return this.editor.getValue()
} else if (sessions[path]) { } else if (this.sessions[path]) {
return sessions[path].getValue() return this.sessions[path].getValue()
} }
} }
/** /**
* returns the path of the currently editing file * Path of the currently editing file
* returns `undefined` if no session is being editer * returns `undefined` if no session is being editer
* * @return {String} path of the current session
* @return {String} path of the current session */
*/ current () {
this.current = function () { if (this.editor.getSession() === this.emptySession) {
if (editor.getSession() === emptySession) {
return 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]) { * Remove the current session from the list of sessions.
delete sessions[currentSession] */
currentSession = null discardCurrentSession () {
if (this.sessions[this.currentSession]) {
delete this.sessions[this.currentSession]
this.currentSession = null
} }
} }
this.discard = function (path) { /**
if (sessions[path]) delete sessions[path] * Remove a session based on its path.
if (currentSession === path) currentSession = null * @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() * Resize the editor, and sets whether or not line wrapping is enabled.
var session = editor.getSession() * @param {boolean} useWrapMode Enable (or disable) wrap mode
*/
resize (useWrapMode) {
this.editor.resize()
const session = this.editor.getSession()
session.setUseWrapMode(useWrapMode) session.setUseWrapMode(useWrapMode)
if (session.getUseWrapMode()) { if (session.getUseWrapMode()) {
var characterWidth = editor.renderer.characterWidth const characterWidth = this.editor.renderer.characterWidth
var contentWidth = editor.container.ownerDocument.getElementsByClassName('ace_scroller')[0].clientWidth const contentWidth = this.editor.container.ownerDocument.getElementsByClassName(
'ace_scroller'
)[0].clientWidth
if (contentWidth > 0) { if (contentWidth > 0) {
session.setWrapLimit(parseInt(contentWidth / characterWidth, 10)) 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) * Adds a new marker to the given `Range`.
if (sessions[source]) { * @param {*} lineColumnPos
return sessions[source].addMarker(currentRange, cssClass) * @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 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]) { * Remove a marker from the session
sessions[source].removeMarker(markerId) * @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 = [] * Clears all the annotations for the current session.
editor.getSession().clearAnnotations() */
} clearAnnotations () {
this.sourceAnnotations = []
this.addAnnotation = function (annotation) { this.editor.getSession().clearAnnotations()
sourceAnnotations[sourceAnnotations.length] = annotation
this.setAnnotations(sourceAnnotations)
}
this.setAnnotations = function (sourceAnnotations) {
editor.getSession().setAnnotations(sourceAnnotations)
} }
this.gotoLine = function (line, col) { /**
editor.focus() * Add an annotation to the current session.
editor.gotoLine(line + 1, col - 1, true) * @param {Object} annotation
*/
addAnnotation (annotation) {
this.sourceAnnotations[this.sourceAnnotations.length] = annotation
this.setAnnotations(this.sourceAnnotations)
} }
this.find = (string) => editor.find(string) /**
* Set a list of annotations to the current session.
this.previousInput = '' * @param {Array<Object>} annotation
this.saveTimeout = null */
// Do setup on initialisation here setAnnotations (sourceAnnotations) {
editor.on('changeSession', function () { this.editor.getSession().setAnnotations(sourceAnnotations)
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
} }
self.previousInput = input
// fire storage update /**
// NOTE: save at most once per 5 seconds * Moves the cursor and focus to the specified line and column number
if (self.saveTimeout) { * @param {number} line
window.clearTimeout(self.saveTimeout) * @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 module.exports = Editor

@ -1,20 +1,19 @@
'use strict' 'use strict'
var csjs = require('csjs-inject') const csjs = require('csjs-inject')
var globlalRegistry = require('../../global/registry') const globlalRegistry = require('../../global/registry')
var styleGuide = require('../ui/styles-guide/theme-chooser') const styleGuide = require('../ui/styles-guide/theme-chooser')
var styles = styleGuide.chooser() const styles = styleGuide.chooser()
class SourceHighlighter { class SourceHighlighter {
constructor (localRegistry) { constructor (localRegistry) {
const self = this this._components = {}
self._components = {} this._components.registry = localRegistry || globlalRegistry
self._components.registry = localRegistry || globlalRegistry
// dependencies // dependencies
self._deps = { this._deps = {
editor: self._components.registry.get('editor').api, editor: this._components.registry.get('editor').api,
config: self._components.registry.get('config').api, config: this._components.registry.get('config').api,
fileManager: self._components.registry.get('filemanager').api, fileManager: this._components.registry.get('filemanager').api,
compilerArtefacts: self._components.registry.get('compilersartefacts').api compilerArtefacts: this._components.registry.get('compilersartefacts').api
} }
this.statementMarker = null this.statementMarker = null
this.fullLineMarker = null this.fullLineMarker = null
@ -26,7 +25,7 @@ class SourceHighlighter {
if (this.fullLineMarker) this._deps.editor.removeMarker(this.fullLineMarker, this.source) if (this.fullLineMarker) this._deps.editor.removeMarker(this.fullLineMarker, this.source)
let lastCompilationResult = this._deps.compilerArtefacts['__last'] let lastCompilationResult = this._deps.compilerArtefacts['__last']
if (location && location.file !== undefined && lastCompilationResult) { if (location && location.file !== undefined && lastCompilationResult) {
var path = lastCompilationResult.getSourceName(location.file) const path = lastCompilationResult.getSourceName(location.file)
if (path) { if (path) {
this.currentSourceLocationFromfileName(lineColumnPos, path) this.currentSourceLocationFromfileName(lineColumnPos, path)
} }
@ -45,7 +44,7 @@ class SourceHighlighter {
this._deps.fileManager.switchFile(this.source) this._deps.fileManager.switchFile(this.source)
} }
var css = csjs` const css = csjs`
.highlightcode { .highlightcode {
position:absolute; position:absolute;
z-index:20; z-index:20;

Loading…
Cancel
Save