Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions project.clj
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
;;;;
;;;; gorilla-repl is licenced to you under the MIT licence. See the file LICENCE.txt for full details.

(defproject gorilla-repl "0.3.5-SNAPSHOT"
(defproject visi/gorilla-repl "0.3.5-SNAPSHOT"
:description "A rich REPL for Clojure in the notebook style."
:url "https://github.com/JonyEpsilon/gorilla-repl"
:license {:name "MIT"}
Expand All @@ -18,7 +18,7 @@
[javax.servlet/servlet-api "2.5"]
[grimradical/clj-semver "0.2.0" :exclusions [org.clojure/clojure]]
[cider/cider-nrepl "0.8.1"]
[org.clojure/tools.nrepl "0.2.3"]]
[org.clojure/tools.nrepl "0.2.7"]]
:main ^:skip-aot gorilla-repl.core
:target-path "target/%s"
:profiles {:uberjar {:aot :all}})
179 changes: 179 additions & 0 deletions resources/public/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
<!DOCTYPE html>
<!--
~ This file is part of gorilla-repl. Copyright (C) 2014-, Jony Hudson.
~
~ gorilla-repl is licenced to you under the MIT licence. See the file LICENCE.txt for full details.
-->

<html id="document">
<head>
<meta charset="UTF-8">

<link rel="shortcut icon" href="favicon.ico"/>

<link href='//fonts.googleapis.com/css?family=Arvo:400,700,400italic,700italic|Lora:400,700,400italic,700italic' rel='stylesheet' type='text/css'>
<link rel="stylesheet" href="jslib/codemirror-4.5/lib/codemirror.css"/>
<link rel="stylesheet" href="css/codemirror-hint.css"/>
<link rel="stylesheet" href="css/worksheet.css"/>
<link rel="stylesheet" href="css/gorilla.css"/>
<link rel="stylesheet" href="css/output.css"/>

<script type="text/javascript"
src="//cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS-MML_SVG-full.js&delayStartupUntil=configured"></script>
<script type="text/javascript" src="jslib/marked/marked.min.js"></script>
<script type="text/javascript" src="jslib/jquery/jquery-1.10.2.min.js"></script>
<script type="text/javascript" src="jslib/underscore/underscore.min.js"></script>
<script type="text/javascript" src="jslib/knockoutjs/knockout-3.0.0.min.js"></script>
<script type="text/javascript" src="jslib/codemirror-4.5/lib/codemirror.js"></script>
<script type="text/javascript" src="jslib/codemirror-4.5/addon/edit/closebrackets.js"></script>
<script type="text/javascript" src="jslib/codemirror-4.5/addon/edit/matchbrackets.js"></script>
<script type="text/javascript" src="jslib/codemirror-4.5/addon/runmode/runmode.js"></script>
<script type="text/javascript" src="jslib/codemirror-4.5/addon/runmode/colorize.js"></script>
<script type="text/javascript" src="jslib/codemirror-4.5/addon/hint/show-hint.js"></script>
<script type="text/javascript" src="jslib/codemirror-4.5/mode/clojure/clojure.js"></script>
<script type="text/javascript" src="jslib/codemirror-4.5/mode/markdown/markdown.js"></script>
<script type="text/javascript" src="jslib/codemirror-4.5/mode/xml/xml.js"></script>
<script type="text/javascript" src="jslib/d3/d3.v3.min.js"></script>
<script type="text/javascript" src="jslib/d3/d3.geo.projection.min.js"></script>
<script type="text/javascript" src="jslib/vega/vega.1.3.3.min.js"></script>
<script type="text/javascript" src="jslib/uuid/uuid.core.js"></script>
<script type="text/javascript" src="jslib/mousetrap/mousetrap.min.js"></script>
<script type="text/javascript" src="js/eventbus.js"></script>
<script type="text/javascript" src="js/commandProcessor.js"></script>
<script type="text/javascript" src="js/repl-ws.js"></script>
<script type="text/javascript" src="js/utils.js"></script>
<script type="text/javascript" src="js/worksheetParser.js"></script>
<script type="text/javascript" src="js/codemirrorVM.js"></script>
<script type="text/javascript" src="js/completions.js"></script>
<script type="text/javascript" src="js/mathJaxViewer.js"></script>
<script type="text/javascript" src="js/outputViewer.js"></script>
<script type="text/javascript" src="js/renderer.js"></script>
<script type="text/javascript" src="js/segment.js"></script>
<script type="text/javascript" src="js/worksheet.js"></script>
<script type="text/javascript" src="js/palette.js"></script>
<script type="text/javascript" src="js/saveDialog.js"></script>
<script type="text/javascript" src="js/codeDialog.js"></script>
<script type="text/javascript" src="js/docViewer.js"></script>
<script type="text/javascript" src="js/main.js"></script>

<title data-bind="text: title">Gorilla REPL</title>
</head>
<body>

<!-- The page stub -->
<div class="status" data-bind="visible: (status() !== '')"><h3 data-bind="html: status">Loading ...</h3></div>
<div class="menu-icon" data-bind="click: handleMenuClick">&#9776;</div>
<div data-bind="template: {name: 'palette-template', data: palette}"></div>
<div data-bind="template: {name: 'code-dialog-template', data: codeDialog}"></div>
<div data-bind="template: {name: 'save-template', data: saveDialog}"></div>
<div data-bind="template: {name: 'doc-template', data: docViewer}"></div>


<div id="contents">
<div class="segment container-segment">
<div class="container-children">
<div data-bind="template: {name: 'segment-template', foreach: worksheet().segments()}">
</div>
</div>
</div>
</div>
<div class="scroll-pad"></div>

<!-- Templates -->

<script type="text/html" id="palette-template">
<div class="modal-overlay" data-bind="visible: shown, click: handleOverlayClick"></div>
<div class="modal" data-bind="visible: shown">
<h3 data-bind="text: caption"></h3>
<div class="modal-content">
<input type="text"
data-bind="hasFocus: focused, value: filterText, valueUpdate: 'input', event: {keydown: handleKeyPress}"/>
<div class="palette-items">
<ul data-bind="foreach: items">
<li data-bind="click: $parent.handleItemClick, attr: {id: ('palette-item-' + $index())}">
<div class="palette-item"
data-bind="html: desc, css: {highlight: ($index() == $parent.highlight())}">
</div>
<!--<div class="command-shortcut" data-bind="text: kb"></div>-->
</li>
</ul>
</div>
</div>
</div>
</script>

<script type="text/html" id="save-template">
<div class="modal-overlay" data-bind="visible: shown, click: handleOverlayClick"></div>
<div class="modal" data-bind="visible: shown">
<h3>Filename (relative to project directory):</h3>
<div class="modal-content">
<input type="text" data-bind="hasFocus: focused, value: filename, valueUpdate: 'input', event: {keydown: handleKeyPress}"/>
<div><div class="modal-button" data-bind="click: handleCancelClick">Cancel</div>
<div class="modal-button highlight" data-bind="click: handleOKClick">OK</div>
</div>
</div>
</div>
</script>

<script type="text/html" id="code-dialog-template">
<div class="modal-overlay" data-bind="visible: shown, click: handleOverlayClick"></div>
<div class="modal" data-bind="visible: shown">
<h3 data-bind="text: caption"></h3>
<div class="modal-content">
<div class="message" data-bind="html: message"></div>
<div class='last-chance'>
<textarea class="last-chance"
data-bind="hasFocus: focused, value: contents, event: {keydown: handleKeyPress}"></textarea>
</div>
<div>
<div class="modal-button highlight" data-bind="text: okButtonText, click: handleOKClick"></div>
</div>
</div>
</div>
</script>

<script type="text/html" id="doc-template">
<div class="doc-viewer" data-bind="visible: shown">
<div class="doc-viewer-content">
<div data-bind="html: doc"></div>
</div>
</div>
</script>

<script type="text/html" id="segment-template">
<div data-bind="template: {name: renderTemplate}"></div>
</script>

<script type="text/html" id="code-segment-template">
<div class="segment code running"
data-bind="css: {selected: active(), running: runningIndicator}, attr: {id: id}">

<div class="segment-main">
<textarea class="codeTextArea mousetrap" data-bind="codemirror: content"></textarea>
</div>
<div class="error-text" data-bind="text: errorText, visible: errorText() !== ''"></div>
<div class="console-text" data-bind="visible: consoleText() !== ''">
<pre><code data-bind="html: consoleText"></code></pre>
</div>
<div class="output"
data-bind="visible: (output() != ''), outputViewer: output, segmentID: id">
</div>
<div class="segment-footer"></div>
</div>
</script>

<script type="text/html" id="free-segment-template">
<div class="segment free" data-bind="css: {selected: active()}, attr: {id: id}, click: handleClick">
<div class="segment-main">
<div class="free-preview" data-bind="mathJaxViewer: renderedContent, visible: !markupVisible()"></div>
<div class="free-markup" data-bind="visible: markupVisible">
<textarea data-bind="codemirror: content"></textarea>
</div>
</div>
<div class="segment-footer"></div>
</div>
</script>


</body>
</html>
84 changes: 55 additions & 29 deletions resources/public/js/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@
var app = function () {
var self = {};

self.conf = {};

self.conf.strings = {};

// Most importantly, the application has a worksheet! This is exposed so that the UI can bind to it, but note that
// you should never change the worksheet directly, as this will leave the event handlers in an inconsistent state.
// Rather you should use the `setWorksheet` function below.
Expand All @@ -19,7 +23,7 @@ var app = function () {
// whenever we change the filename, we update the URL to match
self.filename.subscribe(function (filename) {
if (filename !== "") history.pushState(null, null, "?filename=" + filename);
else history.pushState(null, null, "/worksheet.html");
else history.pushState(null, null, self.conf.worksheetName || "/worksheet.html");
});
// shows the name of the Leiningen project that gorilla was launched from, makes it easier to manage multiple
// tabs with multiple gorilla sessions.
Expand All @@ -39,11 +43,11 @@ var app = function () {
// UI and, if appropriate, load an initial worksheet.
self.start = function (initialFilename) {
// get hold of configuration information from the backend
$.get("/config")
$.get(/^.*\//.exec(window.location.pathname)[0]+"config")
.done(function (data) {
self.config = data;
self.conf = data;
// If we've got the configuration, then start the app
self.project(self.config.project);
self.project(self.conf.project);
// Prepare an empty worksheet so the UI has something to bind to. We do this as if we are loading a
// worksheet on startup, we do it asynchronously, so need to have something in place before starting
// the UI. This is easier than having two paths that both have UI startup code on them. (Although, the
Expand All @@ -52,9 +56,14 @@ var app = function () {
self.setWorksheet(ws, "");

// start the UI
commandProcessor.installCommands(self.config.keymap);
commandProcessor.installCommands(self.conf.keymap);
ko.applyBindings(self, document.getElementById("document"));

// allow the filename to be passed as a parameter
if (!initialFilename && data.filename) {
initialFilename = data.filename;
}

if (initialFilename) loadFromFile(initialFilename);
else setBlankWorksheet();
})
Expand All @@ -67,15 +76,20 @@ var app = function () {
// A helper function to create a new, blank worksheet with some introductory messages in.
var setBlankWorksheet = function () {
var ws = worksheet();
var meinConf = self.conf || {};
var myStrings = self.conf.strings || {};
ws.segments().push(
// Note that the variable ck here is defined in commandProcessor.js, and gives the appropriate
// shortcut key (ctrl or alt) for the platform.
freeSegment("# Gorilla REPL\n\nWelcome to gorilla :-)\n\nShift + enter evaluates code. " +
"Hit " + ck + "+g twice in quick succession or click the menu icon (upper-right corner) " +
"for more commands ...\n\nIt's a good habit to run each worksheet in its own namespace: feel " +
"free to use the declaration we've provided below if you'd like.")
freeSegment(myStrings.introMessage ||
("# Gorilla REPL\n\nWelcome to gorilla :-)\n\nShift + enter evaluates code. " +
"Hit " + ck + "+g twice in quick succession or click the menu icon (upper-right corner) " +
"for more commands ...\n\nIt's a good habit to run each worksheet in its own namespace: feel " +
"free to use the declaration we've provided below if you'd like."))
);
ws.segments().push(codeSegment("(ns " + makeHipNSName() + "\n (:require [gorilla-plot.core :as plot]))"));
ws.segments().push(codeSegment(myStrings.nsSegment ||
("(ns " + (meinConf.nameSpace || makeHipNSName()) +
"\n (:require [gorilla-plot.core :as plot]))")));
self.setWorksheet(ws, "");
// make it easier for the user to get started by highlighting the empty code segment
eventBus.trigger("worksheet:segment-clicked", {id: self.worksheet().segments()[1].id});
Expand All @@ -84,7 +98,7 @@ var app = function () {

// bound to the window's title
self.title = ko.computed(function () {
if (self.filename() === "") return "Gorilla REPL - " + self.project();
if (self.filename() === "") return (self.conf.strings.title || "Gorilla REPL - ") + self.project();
else return self.project() + " : " + self.filename();
});

Expand Down Expand Up @@ -124,11 +138,11 @@ var app = function () {

// Helpers for loading and saving the worksheet - called by the various command handlers
var saveToFile = function (filename, successCallback) {
$.post("/save", {
$.post(/^.*\//.exec(window.location.pathname)[0]+"save", {
"worksheet-filename": filename,
"worksheet-data": self.worksheet().toClojure()
}).done(function () {
self.flashStatusMessage("Saved: " + filename);
if (!self.conf.hideSavedMsg) self.flashStatusMessage("Saved: " + filename);
if (successCallback) successCallback();
}).fail(function () {
self.flashStatusMessage("Failed to save worksheet: " + filename, 2000);
Expand All @@ -137,21 +151,23 @@ var app = function () {

var loadFromFile = function (filename) {
// ask the backend to load the data from disk
$.get("/load", {"worksheet-filename": filename})
.done(function (data) {
if (data['worksheet-data']) {
// parse and construct the new worksheet
var segments = worksheetParser.parse(data["worksheet-data"]);
var ws = worksheet();
ws.segments = ko.observableArray(segments);
// show it in the editor
self.setWorksheet(ws, filename);
// highlight the first code segment if it exists
var codeSegments = _.filter(self.worksheet().segments(), function(s) {return s.type === 'code'});
if (codeSegments.length > 0)
eventBus.trigger("worksheet:segment-clicked", {id: codeSegments[0].id});
}
})
$.get(/^.*\//.exec(window.location.pathname)[0]+"load", {"worksheet-filename": filename})
.done(function (data) {
if (data['worksheet-data']) {
// parse and construct the new worksheet
var segments = worksheetParser.parse(data["worksheet-data"]);
var ws = worksheet();
ws.segments = ko.observableArray(segments);
// show it in the editor
self.setWorksheet(ws, filename);
// highlight the first code segment if it exists
var codeSegments = _.filter(self.worksheet().segments(), function(s) {return s.type === 'code'});
if (codeSegments.length > 0) {
eventBus.trigger("worksheet:segment-clicked", {id: codeSegments[0].id});
if (self.conf.recalcAllOnLoad) eventBus.trigger("worksheet:evaluate-all");
}
}
})
.fail(function () {
self.flashStatusMessage("Failed to load worksheet: " + filename, 2000);
});
Expand All @@ -178,7 +194,7 @@ var app = function () {
self.palette.show("Scanning for files ...", []);
$.ajax({
type: "GET",
url: "/gorilla-files",
url: /^.*\//.exec(window.location.pathname)[0]+"gorilla-files",
success: function (data) {
var paletteFiles = data.files.map(function (c) {
return {
Expand All @@ -204,6 +220,16 @@ var app = function () {
} else self.saveDialog.show();
});

// Save only if the worksheet is named. Used for autosave.
eventBus.on("app:save-named", function () {
var fname = self.filename();
// if we already have a filename, save to it. Else, prompt for a name.
if (fname !== "" && self.conf.autosave) {
saveToFile(fname);
}
});


eventBus.on("app:saveas", function () {
var fname = self.filename();
self.saveDialog.show(fname);
Expand Down
Loading