diff --git a/src/app.js b/src/app.js
index 04158f3e32..745ba439ad 100644
--- a/src/app.js
+++ b/src/app.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()
$('', {
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($('
' + name + ''))
}
- 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
diff --git a/src/app/editor.js b/src/app/editor.js
index 02a3c61b72..f92f07fdf4 100644
--- a/src/app/editor.js
+++ b/src/app/editor.js
@@ -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)
}