Merge pull request #401 from ethereum/editor-rewrite

Rewrite the editor based on the  files API
pull/1/head
Alex Beregszaszi 8 years ago committed by GitHub
commit 5c9ed0ec28
  1. 157
      src/app.js
  2. 163
      src/app/editor.js

@ -1,4 +1,4 @@
/* global alert, confirm, prompt, Option, Worker, chrome */
/* global alert, confirm, prompt, FileReader, Option, Worker, chrome */
'use strict'
var $ = require('jquery')
@ -10,6 +10,7 @@ var GistHandler = require('./app/gist-handler')
var gistHandler = new GistHandler()
var Storage = require('./app/storage')
var Files = require('./app/files')
var Config = require('./app/config')
var Editor = require('./app/editor')
var Renderer = require('./app/renderer')
@ -22,6 +23,8 @@ var EventManager = require('./lib/eventManager')
var StaticAnalysis = require('./app/staticanalysis/staticAnalysisView')
var OffsetToLineColumnConverter = require('./lib/offsetToLineColumnConverter')
var examples = require('./app/example-contracts')
// The event listener needs to be registered as early as possible, because the
// parent will send the message upon the "load" event.
var filesToLoad = null
@ -40,16 +43,31 @@ var run = function () {
var self = this
this.event = new EventManager()
var storage = new Storage()
var files = new Files(storage)
var config = new Config(storage)
var currentFile
// return all the files, except the temporary/readonly ones
function packageFiles () {
return files.list()
.filter(function (path) { if (!files.isReadOnly(path)) { return path } })
.map(function (path) { return { content: files.get(path) } })
}
function createNonClashingName (path) {
var counter = ''
while (files.exists(path + counter)) {
counter = (counter | 0) + 1
}
return path + counter
}
// Add files received from remote instance (i.e. another browser-solidity)
function loadFiles (files) {
for (var f in files) {
storage.loadFile(f, files[f].content)
files.set(createNonClashingName(f), files[f].content)
}
// Set the first file as current tab
editor.setCacheFile(Object.keys(files)[0])
updateFiles()
switchToNextFile()
}
// Replace early callback with instant response
@ -87,6 +105,11 @@ var run = function () {
})
})
// insert ballot contract if there are no files available
if (!loadingFromGist && (files.list().length === 0)) {
files.set(examples.ballot.name, examples.ballot.content)
}
// ----------------- Chrome cloud storage sync --------------------
function chromeCloudSync () {
@ -103,11 +126,11 @@ var run = function () {
console.log('comparing to cloud', key, resp)
if (typeof resp[key] !== 'undefined' && obj[key] !== resp[key] && confirm('Overwrite "' + key + '"? Click Ok to overwrite local file with file from cloud. Cancel will push your local file to the cloud.')) {
console.log('Overwriting', key)
storage.set(key, resp[key])
files.set(key, resp[key])
updateFiles()
} else {
console.log('add to obj', obj, key)
obj[key] = storage.get(key)
obj[key] = files.get(key)
}
done++
if (done >= count) {
@ -118,9 +141,9 @@ var run = function () {
})
}
for (var y in storage.keys()) {
for (var y in files.list()) {
console.log('checking', y)
obj[y] = storage.get(y)
obj[y] = files.get(y)
count++
check(y)
}
@ -131,7 +154,7 @@ var run = function () {
// ----------------- editor ----------------------
var editor = new Editor(loadingFromGist, storage)
var editor = new Editor()
// ----------------- tabbed menu -------------------
$('#options li').click(function (ev) {
@ -155,7 +178,7 @@ var run = function () {
$('#gist').click(function () {
if (confirm('Are you sure you want to publish all your files anonymously as a public gist on github.com?')) {
var files = editor.packageFiles()
var files = packageFiles()
var description = 'Created using browser-solidity: Realtime Ethereum Contract Compiler and Runtime. \n Load this file by pasting this gists URL or ID at https://ethereum.github.io/browser-solidity/#version=' + queryParams.get().version + '&optimize=' + queryParams.get().optimize + '&gist='
$.ajax({
@ -182,7 +205,7 @@ var run = function () {
if (target === null) {
return
}
var files = editor.packageFiles()
var files = packageFiles()
$('<iframe/>', {
src: target,
style: 'display:none;',
@ -196,12 +219,9 @@ var run = function () {
var FILE_SCROLL_DELTA = 300
$('.newFile').on('click', function () {
editor.newFile()
updateFiles()
$filesEl.animate({ left: Math.max((0 - activeFilePos() + (FILE_SCROLL_DELTA / 2)), 0) + 'px' }, 'slow', function () {
reAdjust()
})
var newName = createNonClashingName('Untitled')
files.set(newName, '')
switchToFile(newName)
})
// ----------------- file upload -------------
@ -210,14 +230,15 @@ var run = function () {
var fileList = $('input.inputFile')[0].files
for (var i = 0; i < fileList.length; i++) {
var name = fileList[i].name
if (!editor.hasFile(name) || confirm('The file ' + name + ' already exists! Would you like to overwrite it?')) {
editor.uploadFile(fileList[i], updateFiles)
if (!files.exists(name) || confirm('The file ' + name + ' already exists! Would you like to overwrite it?')) {
var fileReader = new FileReader()
fileReader.onload = function (ev) {
files.set(name, ev.target.result)
switchToFile(name)
}
fileReader.readAsText(fileList[i])
}
}
$filesEl.animate({ left: Math.max((0 - activeFilePos() + (FILE_SCROLL_DELTA / 2)), 0) + 'px' }, 'slow', function () {
reAdjust()
})
})
// Switch tab
@ -248,15 +269,14 @@ var run = function () {
$fileNameInputEl.off('keyup')
if (newName !== originalName && confirm(
editor.hasFile(newName)
files.exists(newName)
? 'Are you sure you want to overwrite: ' + newName + ' with ' + originalName + '?'
: 'Are you sure you want to rename: ' + originalName + ' to ' + newName + '?')) {
storage.rename(originalName, newName)
editor.renameSession(originalName, newName)
editor.setCacheFile(newName)
files.rename(originalName, newName)
switchToFile(newName)
editor.discard(originalName)
}
updateFiles()
return false
}
@ -269,10 +289,9 @@ var run = function () {
var name = $(this).parent().find('.name').text()
if (confirm('Are you sure you want to remove: ' + name + ' from local storage?')) {
storage.remove(name)
editor.removeSession(name)
editor.setNextFile(name)
updateFiles()
files.remove(name)
switchToNextFile()
editor.discard(name)
}
return false
})
@ -280,32 +299,49 @@ var run = function () {
editor.event.register('sessionSwitched', updateFiles)
function switchToFile (file) {
editor.setCacheFile(file)
updateFiles()
currentFile = file
if (files.isReadOnly(file)) {
editor.openReadOnly(file, files.get(file))
} else {
editor.open(file, files.get(file))
}
}
function switchToNextFile () {
var fileList = Object.keys(files.list())
if (fileList.length) {
switchToFile(fileList[0])
}
}
switchToNextFile()
// Synchronise tab list with file names known to the editor
function updateFiles () {
var $filesEl = $('#files')
var files = editor.getFiles()
var fileNames = Object.keys(files.list())
$filesEl.find('.file').remove()
$('#output').empty()
for (var f in files) {
var name = files[f]
for (var f in fileNames) {
var name = fileNames[f]
$filesEl.append($('<li class="file"><span class="name">' + name + '</span><span class="remove"><i class="fa fa-close"></i></span></li>'))
}
if (editor.cacheFileIsPresent()) {
var currentFileName = editor.getCacheFile()
var active = $('#files .file').filter(function () { return $(this).find('.name').text() === currentFileName })
var currentFileOpen = !!currentFile
if (currentFileOpen) {
var active = $('#files .file').filter(function () { return $(this).find('.name').text() === currentFile })
active.addClass('active')
editor.resetSession()
}
$('#input').toggle(editor.cacheFileIsPresent())
$('#output').toggle(editor.cacheFileIsPresent())
reAdjust()
$('#input').toggle(currentFileOpen)
$('#output').toggle(currentFileOpen)
$filesEl.animate({ left: Math.max((0 - activeFilePos() + (FILE_SCROLL_DELTA / 2)), 0) + 'px' }, 'slow', function () {
reAdjust()
})
}
var $filesWrapper = $('.files-wrapper')
@ -368,8 +404,6 @@ var run = function () {
})
})
updateFiles()
// ----------------- resizeable ui ---------------
var EDITOR_WINDOW_SIZE = 'editorWindowSize'
@ -465,15 +499,10 @@ var run = function () {
})
}
// FIXME: at some point we should invalidate the cache
var cachedRemoteFiles = {}
function handleImportCall (url, cb) {
var githubMatch
if (editor.hasFile(url)) {
cb(null, editor.getFile(url))
} else if (url in cachedRemoteFiles) {
cb(null, cachedRemoteFiles[url])
if (files.exists(url)) {
cb(null, files.get(url))
} else if ((githubMatch = /^(https?:\/\/)?(www.)?github.com\/([^/]*\/[^/]*)\/(.*)/.exec(url))) {
handleGithubCall(githubMatch[3], githubMatch[4], function (err, content) {
if (err) {
@ -481,7 +510,8 @@ var run = function () {
return
}
cachedRemoteFiles[url] = content
// FIXME: at some point we should invalidate the cache
files.addReadOnly(url, content)
cb(null, content)
})
} else if (/^[^:]*:\/\//.exec(url)) {
@ -506,9 +536,8 @@ var run = function () {
this.statementMarker = null
this.fullLineMarker = null
if (lineColumnPos) {
var name = editor.getCacheFile() // current opened tab
var source = compiler.lastCompilationResult.data.sourceList[location.file] // auto switch to that tab
if (name !== source) {
if (currentFile !== source) {
switchToFile(source)
}
this.statementMarker = editor.addMarker(lineColumnPos, 'highlightcode')
@ -569,12 +598,12 @@ var run = function () {
var rendererAPI = {
error: (file, error) => {
if (file === editor.getCacheFile()) {
if (file === currentFile) {
editor.addAnnotation(error)
}
},
errorClick: (errFile, errLine, errCol) => {
if (errFile !== editor.getCacheFile() && editor.hasFile(errFile)) {
if (errFile !== currentFile && files.exists(errFile)) {
switchToFile(errFile)
}
editor.gotoLine(errLine, errCol)
@ -623,9 +652,9 @@ var run = function () {
function runCompiler () {
var files = {}
var target = editor.getCacheFile()
var target = currentFile
files[target] = editor.getValue()
files[target] = editor.get(currentFile)
compiler.compile(files, target)
}
@ -635,7 +664,7 @@ var run = function () {
var saveTimeout = null
function editorOnChange () {
var input = editor.getValue()
var input = editor.get(currentFile)
// if there's no change, don't do anything
if (input === previousInput) {
@ -649,8 +678,8 @@ var run = function () {
window.clearTimeout(saveTimeout)
}
saveTimeout = window.setTimeout(function () {
var input = editor.getValue()
editor.setCacheFileContent(input)
var input = editor.get(currentFile)
files.set(currentFile, input)
}, 5000)
// special case: there's nothing else to do

@ -1,122 +1,73 @@
/* global FileReader */
'use strict'
var EventManager = require('../lib/eventManager')
var examples = require('./example-contracts')
var ace = require('brace')
var Range = ace.acequire('ace/range').Range
require('../mode-solidity.js')
function Editor (doNotLoadStorage, storage) {
var SOL_CACHE_FILE = null
function Editor () {
var editor = ace.edit('input')
document.getElementById('input').editor = editor // required to access the editor during tests
var event = new EventManager()
this.event = event
var sessions = {}
var sourceAnnotations = []
var readOnlySessions = {}
var currentSession
this.addMarker = function (lineColumnPos, cssClass) {
var currentRange = new Range(lineColumnPos.start.line, lineColumnPos.start.column, lineColumnPos.end.line, lineColumnPos.end.column)
return editor.session.addMarker(currentRange, cssClass)
}
var emptySession = createSession('')
this.removeMarker = function (markerId) {
editor.session.removeMarker(markerId)
}
this.newFile = function () {
var untitledCount = ''
while (storage.exists('Untitled' + untitledCount)) {
untitledCount = (untitledCount - 0) + 1
}
this.setCacheFile('Untitled' + untitledCount)
this.setCacheFileContent('')
}
this.uploadFile = function (file, callback) {
var fileReader = new FileReader()
var name = file.name
var self = this
fileReader.onload = function (e) {
self.setCacheFile(name)
self.setCacheFileContent(e.target.result)
callback()
}
fileReader.readAsText(file)
}
this.setCacheFileContent = function (content) {
storage.set(SOL_CACHE_FILE, content)
}
this.setCacheFile = function (cacheFile) {
SOL_CACHE_FILE = cacheFile
}
this.getCacheFile = function () {
return SOL_CACHE_FILE
}
this.cacheFileIsPresent = function () {
return !!SOL_CACHE_FILE
}
this.setNextFile = function (fileKey) {
var index = this.getFiles().indexOf(fileKey)
this.setCacheFile(this.getFiles()[ Math.max(0, index - 1) ])
function createSession (content) {
var s = new ace.EditSession(content, 'ace/mode/javascript')
s.setUndoManager(new ace.UndoManager())
s.setTabSize(4)
s.setUseSoftTabs(true)
return s
}
this.resetSession = function () {
editor.setSession(sessions[this.getCacheFile()])
function switchSession (path) {
currentSession = path
editor.setSession(sessions[currentSession])
editor.setReadOnly(readOnlySessions[currentSession])
editor.focus()
}
this.removeSession = function (fileKey) {
delete sessions[fileKey]
}
this.renameSession = function (oldFileKey, newFileKey) {
if (oldFileKey !== newFileKey) {
sessions[newFileKey] = sessions[oldFileKey]
this.removeSession(oldFileKey)
this.open = function (path, content) {
if (!sessions[path]) {
var session = createSession(content)
sessions[path] = session
readOnlySessions[path] = false
}
switchSession(path)
}
this.hasFile = function (name) {
return this.getFiles().indexOf(name) !== -1
this.openReadOnly = function (path, content) {
if (!sessions[path]) {
var session = createSession(content)
sessions[path] = session
readOnlySessions[path] = true
}
switchSession(path)
}
this.getFile = function (name) {
return storage.get(name)
this.get = function (path) {
if (currentSession === path) {
return editor.getValue()
}
}
function getFiles () {
var files = []
storage.keys().forEach(function (f) {
// NOTE: as a temporary measure do not show the config file in the editor
if (f !== '.browser-solidity.json') {
files.push(f)
if (!sessions[f]) sessions[f] = newEditorSession(f)
}
})
return files
this.current = function (path) {
if (editor.getSession() === emptySession) {
return
}
return currentSession
}
this.getFiles = getFiles
this.packageFiles = function () {
var files = {}
var filesArr = this.getFiles()
for (var f in filesArr) {
files[filesArr[f]] = {
content: storage.get(filesArr[f])
}
this.discard = function (path) {
if (currentSession !== path) {
delete sessions[path]
}
return files
}
this.resize = function () {
@ -133,8 +84,13 @@ function Editor (doNotLoadStorage, storage) {
}
}
this.getValue = function () {
return editor.getValue()
this.addMarker = function (lineColumnPos, cssClass) {
var currentRange = new Range(lineColumnPos.start.line, lineColumnPos.start.column, lineColumnPos.end.line, lineColumnPos.end.column)
return editor.session.addMarker(currentRange, cssClass)
}
this.removeMarker = function (markerId) {
editor.session.removeMarker(markerId)
}
this.clearAnnotations = function () {
@ -156,15 +112,6 @@ function Editor (doNotLoadStorage, storage) {
editor.gotoLine(line + 1, col - 1, true)
}
function newEditorSession (filekey) {
var s = new ace.EditSession(storage.get(filekey), 'ace/mode/javascript')
s.setUndoManager(new ace.UndoManager())
s.setTabSize(4)
s.setUseSoftTabs(true)
sessions[filekey] = s
return s
}
// Do setup on initialisation here
editor.on('changeSession', function () {
event.trigger('sessionSwitched', [])
@ -178,24 +125,6 @@ function Editor (doNotLoadStorage, storage) {
editor.commands.bindKeys({ 'ctrl-t': null })
editor.commands.bindKeys({ 'ctrl-f': null })
if (doNotLoadStorage) {
return
}
var files = getFiles()
if (files.length === 0) {
files.push(examples.ballot.name)
storage.set(examples.ballot.name, examples.ballot.content)
}
this.setCacheFile(files[0])
for (var x in files) {
sessions[files[x]] = newEditorSession(files[x])
}
editor.setSession(sessions[this.getCacheFile()])
editor.resize(true)
}

Loading…
Cancel
Save