Skip to content

Commit

Permalink
worksheet mode
Browse files Browse the repository at this point in the history
  • Loading branch information
MasseGuillaume committed Jan 18, 2017
1 parent f6ad2c2 commit 4ac6bf0
Show file tree
Hide file tree
Showing 17 changed files with 134 additions and 78 deletions.
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
Expand Up @@ -198,4 +198,4 @@
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
limitations under the License.
14 changes: 6 additions & 8 deletions api/src/main/scala/api/Api.scala
Original file line number Diff line number Diff line change
Expand Up @@ -99,12 +99,13 @@ object ScalaTarget {
}

object Inputs {
val defaultCode =
"""|class Playground {
| 1 + 1
|}""".stripMargin
val defaultCode =
"""|List("Hello", "World").mkString("", ", ", "!")
|
|help""".stripMargin

def default = Inputs(
isInstrumented = true,
code = defaultCode,
target = ScalaTarget.Jvm.default,
libraries = Set(),
Expand All @@ -114,6 +115,7 @@ object Inputs {
}

case class Inputs(
isInstrumented: Boolean,
code: String,
target: ScalaTarget,
libraries: Set[ScalaDependency],
Expand Down Expand Up @@ -220,10 +222,6 @@ case class Instrumentation(position: Position, render: Render)
sealed trait Render
case class Value(v: String, className: String) extends Render

case class Markdown(a: String, folded: Boolean = false) extends Render {
def stripMargin = Markdown(a.stripMargin)
def fold = copy(folded = true)
}
case class Html(a: String, folded: Boolean = false) extends Render {
def stripMargin = copy(a = a.stripMargin)
def fold = copy(folded = true)
Expand Down
36 changes: 36 additions & 0 deletions api/src/main/scala/api/Runtime.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package api

package object runtime {
implicit class HtmlHelper(val sc: StringContext) extends AnyVal {
def html(args: Any*) = Html(sc.s(args: _*))
def htmlRaw(args: Any*) = Html(sc.raw(args: _*))
}

val help = {

val Dot = "<kbd>&nbsp;&nbsp;.&nbsp;&nbsp;</kbd>"
val Space = s"""<kbd>${"&nbsp;" * 40}</kbd>"""
val Ctrl = "<kbd class='pc'>Ctrl&nbsp;&nbsp;</kbd>"
val Cmd = "<kbd class='mac'>&nbsp;⌘&nbsp;</kbd>"
val Enter = "<kbd>&nbsp;&nbsp;Enter&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</kbd>"
val F2 = "<kbd>&nbsp;F2&nbsp;</kbd>"
val F7 = "<kbd>&nbsp;F7&nbsp;</kbd>"
val Esc = "<kbd>&nbsp;Esc&nbsp;</kbd>"
val github = "https://github.com/scalacenter/scastie"
val sublime = "http://sublime-text-unofficial-documentation.readthedocs.org/en/latest/reference/keyboard_shortcuts_osx.html"

html"""|<h1>Welcome to Scastie!</h1>
|Scastie is an interractive playground.
|Evaluate expressions with $Ctrl $Cmd + $Enter.
|Clear the output with $Esc.
|<pre>
|clear $Esc
|run $Ctrl $Cmd + $Enter
|toggle theme $F2
|<a target="_blank" href="$sublime">Sublime Text Keyboard Shortcuts</a>
|</pre>
|The source code is available at <a target="_blank" href="$github">scalacenter/scastie</a>,
|published under the Apache2 license
|""".stripMargin.fold
}
}
7 changes: 5 additions & 2 deletions client/src/main/assets/_app.less
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ body, .root, .app, .main-pannel, .pannel, .sidebar,
height: 100%;
}

body.mac kbd.pc { display: none }
body.pc kbd.mac { display: none }

.app {
@sidebar-width: 75px;
@sidebar-border: 1px;
Expand Down Expand Up @@ -97,7 +100,7 @@ body, .root, .app, .main-pannel, .pannel, .sidebar,
background-color: @base03;
color: @base02;
}
&:hover, &.console {
&:hover, &.toggle {
color: @base1;
}
}
Expand All @@ -116,7 +119,7 @@ body, .root, .app, .main-pannel, .pannel, .sidebar,
background-color: @base3;
color: @base2;
}
&:hover, &.console {
&:hover, &.toggle {
color: @base01;
}
}
Expand Down
22 changes: 12 additions & 10 deletions client/src/main/scala/client/App.scala
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,15 @@ object App {
inputs: Inputs = Inputs.default,
outputs: Outputs = Outputs()
) {
def setRunning(v: Boolean) = copy(running = v)
def toogleTheme = copy(dark = !dark)
def toogleConsole = copy(console = !console)
def setRunning(v: Boolean) = copy(running = v)
def toggleTheme = copy(dark = !dark)
def toggleConsole = copy(console = !console)
def toggleInstrumentation = copy(inputs =
inputs.copy(isInstrumented = !inputs.isInstrumented)
)

def openConsole = copy(console = true)
def toogleSidebar = copy(sideBarClosed = !sideBarClosed)
def toggleSidebar = copy(sideBarClosed = !sideBarClosed)
def log(line: String): State = log(Seq(line))
def log(lines: Seq[String]): State =
copy(outputs = outputs.copy(console = outputs.console ++ lines))
Expand Down Expand Up @@ -96,9 +100,6 @@ object App {
def onopen(e: Event): Unit = direct.modState(_.log("Connected.\n"))
def onmessage(e: MessageEvent): Unit = {
val progress = uread[PasteProgress](e.data.toString)
if (progress.timeout) {
window.alert("Evaluation timeout")
}
direct.modState(
_.addOutputs(
progress.compilationInfos,
Expand Down Expand Up @@ -200,9 +201,10 @@ object App {
}
}

def toogleTheme(e: ReactEventI): Callback = toogleTheme()
def toogleTheme(): Callback = scope.modState(_.toogleTheme)
def toogleConsole(e: ReactEventI): Callback = scope.modState(_.toogleConsole)
def toggleTheme(e: ReactEventI): Callback = toggleTheme()
def toggleTheme(): Callback = scope.modState(_.toggleTheme)
def toggleConsole(e: ReactEventI): Callback = scope.modState(_.toggleConsole)
def toggleInstrumentation(e: ReactEventI): Callback = scope.modState(_.toggleInstrumentation)
}

val component = ReactComponentB[(RouterCtl[Page], Option[Snippet])]("App")
Expand Down
6 changes: 6 additions & 0 deletions client/src/main/scala/client/Client.scala
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,12 @@ object Client extends JSApp {

@JSExport
override def main(): Unit = {
val isMac = dom.window.navigator.userAgent.contains("Mac")

dom.document.body.className =
if (isMac) "mac"
else "pc"

val cont =
dom.document.createElement("div").asInstanceOf[dom.raw.HTMLDivElement]
cont.className = "root"
Expand Down
2 changes: 1 addition & 1 deletion client/src/main/scala/client/Editor.scala
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ object Editor {

CodeMirror.commands.solarizedToggle =
(editor: CodeMirrorEditor2) => {
backend.toogleTheme().runNow
backend.toggleTheme().runNow
}

scope.modState(_.copy(editor = Some(editor)))
Expand Down
23 changes: 17 additions & 6 deletions client/src/main/scala/client/Views.scala
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ object SideBar {

val editor =
if (state.running) {
div(`class` := "sk-folding-cube",
div(`class` := "sk-folding-cube", title := "Running...",
onClick ==> setView(View.Editor))(
div(`class` := "sk-cube1 sk-cube"),
div(`class` := "sk-cube2 sk-cube"),
Expand All @@ -85,21 +85,32 @@ object SideBar {
} else {
if (View.Editor == state.view) {
// RUN
mediaPlay(onClick ==> run2, `class` := "runnable")
mediaPlay(onClick ==> run2, `class` := "runnable", title := "Run")
} else {
mediaPlay(onClick ==> setView(View.Editor))
mediaPlay(onClick ==> setView(View.Editor), title := "Edit code")
}
}

val consoleSelected =
if(state.console) TagMod(`class` := "console selected")
if(state.console) TagMod(`class` := "toggle selected")
else EmptyTag

val instrumentationSelected =
if(state.inputs.isInstrumented) TagMod(`class` := "toggle selected")
else EmptyTag

nav(`class` := s"sidebar $theme")(
ul(
li(selected(View.Editor))(editor),
li(selected(View.Settings))(cog(onClick ==> setView(View.Settings))),
li(consoleSelected)(terminal(onClick ==> toogleConsole))
li(selected(View.Settings))(
cog(onClick ==> setView(View.Settings))
),
li(consoleSelected, title := "Toggle console")(
terminal(onClick ==> toggleConsole)
),
li(instrumentationSelected, title := "Toggle worksheet")(
iconic.script(onClick ==> toggleInstrumentation)
)
)
)
}.build
Expand Down
33 changes: 22 additions & 11 deletions instrumentation/src/main/scala/instumentation/Instument.scala
Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,20 @@ object Instrument {
private val instrumentationMethod = "instrumentations$"
private val instrumentationMap = "instrumentationMap$"

private def posToApi(position: Position) = {
private def posToApi(position: Position, offset: Int) = {
def tuple2(v1: Int, v2: Int) = Seq(Lit(v1), Lit(v2))

val lits =
position match {
case Position.None => tuple2(0, 0)
case Position.Range(input, start, end) =>
tuple2(start.offset, end.offset)
tuple2(start.offset - offset, end.offset - offset)
}

Term.Apply(Term.Name("api.Position"), lits)
}

def instrumentOne(term: Term, tpeTree: Option[Type] = None): Patch = {
def instrumentOne(term: Term, tpeTree: Option[Type], offset: Int): Patch = {

val treeQuote =
tpeTree match {
Expand All @@ -34,14 +34,14 @@ object Instrument {
Seq(
"locally {",
treeQuote + "; ",
s"$instrumentationMap(${posToApi(term.pos)}) = scastie.runtime.Runtime.render(t);",
s"$instrumentationMap(${posToApi(term.pos, offset)}) = scastie.runtime.Runtime.render(t);",
"t}"
).mkString("")

Patch(term.tokens.head, term.tokens.last, replacement)
}

private def instrument(source: Source): String = {
private def instrument(source: Source, offset: Int): String = {

val instrumentedCodePatches =
source.stats.collect {
Expand All @@ -61,10 +61,10 @@ object Instrument {

instrumentationMapPatch +:
c.templ.stats.get.collect {
case term: Term => instrumentOne(term)
case vl: Defn.Val => instrumentOne(vl.rhs, vl.decltpe)
case term: Term => instrumentOne(term, None, offset)
case vl: Defn.Val => instrumentOne(vl.rhs, vl.decltpe, offset)
case vr: Defn.Var if vr.rhs.nonEmpty =>
instrumentOne(vr.rhs.get, vr.decltpe)
instrumentOne(vr.rhs.get, vr.decltpe, offset)
}
}
}.flatten
Expand All @@ -82,9 +82,20 @@ object Instrument {
}

def apply(code: String): String = {
code.parse[Source] match {
case parsers.Parsed.Success(k) => instrument(k)
case _ => code
val prelude =
s"""|import api.runtime._
|class $instrumnedClass {""".stripMargin

val code0 =
s"""|$prelude
|$code
|}""".stripMargin

code0.parse[Source] match {
case parsers.Parsed.Success(k) =>
instrument(k, offset = prelude.length + 1)
case _ =>
code0
}
}
}
7 changes: 4 additions & 3 deletions instrumentation/src/test/resources/a/instrumented.scala
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import api.runtime._
class Playground { private val instrumentationMap$ = scala.collection.mutable.Map.empty[api.Position, api.Render];def instrumentations$ = instrumentationMap$.toList.map{ case (pos, r) => api.Instrumentation(pos, r) };
val a = locally {val t = 1 + 1; instrumentationMap$(api.Position(29, 34)) = scastie.runtime.Runtime.render(t);t}
val a = locally {val t = 1 + 1; instrumentationMap$(api.Position(8, 13)) = scastie.runtime.Runtime.render(t);t}

locally {val t = 1 +
a; instrumentationMap$(api.Position(38, 47)) = scastie.runtime.Runtime.render(t);t}
locally {val t = 1 +
a; instrumentationMap$(api.Position(15, 22)) = scastie.runtime.Runtime.render(t);t}
}
object Main {
val playground = new Playground
Expand Down
8 changes: 3 additions & 5 deletions instrumentation/src/test/resources/a/original.scala
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
class Playground {
val a = 1 + 1
val a = 1 + 1

1 +
a
}
1 +
a

This file was deleted.

This file was deleted.

7 changes: 0 additions & 7 deletions runtime-dotty/src/main/scala/scastie.runtime/Runtime.scala
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import upickle.Js._
object Runtime {
def render[T](a: T)(implicit ct: scala.reflect.ClassTag[T]): Render = {
a match {
case md: Markdown => md
case html: Html => html
case v => Value(a.toString.take(1000), ct.toString)
}
Expand All @@ -24,12 +23,6 @@ object Runtime {
"v" -> Str(v),
"className" -> Str(className)
)
case Markdown(a, folded) =>
Obj(
"$type" -> Str("api.Markdown"),
"a" -> Str(a),
"folded" -> write2(folded)
)

case Html(a, folded) =>
Obj(
Expand Down
1 change: 0 additions & 1 deletion runtime-scala/src/main/scala/scastie.runtime/Runtime.scala
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ object Runtime {
def render[T: pprint.PPrint](a: T)(implicit tp: pprint.TPrint[T]): Render = {
import pprint.Config.Defaults._
a match {
case md: Markdown => md
case html: Html => html
case v => Value(pprint.tokenize(v).mkString, tp.render)
}
Expand Down
Loading

0 comments on commit 4ac6bf0

Please sign in to comment.