From 9f8e60b7c7ae3df5506f4f56a92081c634ecf7a6 Mon Sep 17 00:00:00 2001
From: "scala-center-steward[bot]"
<111975575+scala-center-steward[bot]@users.noreply.github.com>
Date: Tue, 6 Aug 2024 00:30:27 +0000
Subject: [PATCH] Reformat with scalafmt 3.8.3
Executed command: scalafmt --non-interactive
---
.../com.olegych.scastie.api/ApiModels.scala | 100 ++---
.../CompilerInfo.scala | 25 +-
.../ConsoleOutput.scala | 22 +-
.../com.olegych.scastie.api/Inputs.scala | 62 ++-
.../Instrumentation.scala | 32 +-
.../com.olegych.scastie.api/Outputs.scala | 31 +-
.../ProcessOutput.scala | 19 +-
.../RuntimeError.scala | 36 +-
.../com.olegych.scastie.api/SbtState.scala | 18 +-
.../ScalaJsResult.scala | 21 +-
.../com.olegych.scastie.api/ScalaTarget.scala | 133 +++---
.../ScalaTargetType.scala | 18 +-
.../ScalaVersions.scala | 20 +-
.../com.olegych.scastie.api/SnippetId.scala | 18 +-
.../SnippetProgress.scala | 65 +--
.../StatusProgress.scala | 30 +-
.../com.olegych.scastie.api/TaskId.scala | 4 +-
.../DispatchActor.scala | 129 +++---
.../LoadBalancer.scala | 33 +-
.../ProgressActor.scala | 33 +-
.../com.olegych.scastie.balancer/Server.scala | 21 +-
.../StatusActor.scala | 37 +-
.../LoadBalancerRecoveryTest.scala | 134 +++---
.../LoadBalancerTest.scala | 30 +-
.../LoadBalancerTestUtils.scala | 32 +-
.../TestUtils.scala | 2 +
.../AnsiColorFormatter.scala | 56 +--
.../ClientMain.scala | 71 +--
.../ConsoleState.scala | 10 +-
.../EmbeddedOptions.scala | 166 +++----
.../EventStream.scala | 35 +-
.../com.olegych.scastie.client/Global.scala | 72 ++-
.../HTMLFormatter.scala | 21 +-
.../LocalStorage.scala | 4 +-
.../ModalState.scala | 27 +-
.../RestApiClient.scala | 59 ++-
.../com.olegych.scastie.client/Routing.scala | 86 ++--
.../RoutingADT.scala | 43 +-
.../ScastieBackend.scala | 416 ++++++++----------
.../ScastieState.scala | 369 +++++++---------
.../StatusState.scala | 2 +-
.../com.olegych.scastie.client/Views.scala | 23 +-
.../components/BuildSettings.scala | 54 ++-
.../components/ClearButton.scala | 20 +-
.../components/CodeSnippets.scala | 145 +++---
.../components/Console.scala | 56 ++-
.../components/CopyModal.scala | 2 +-
.../components/DesktopButton.scala | 16 +-
.../components/DownloadButton.scala | 26 +-
.../components/EditorTopBar.scala | 141 +++---
.../components/EmbeddedOverlay.scala | 17 +-
.../components/FormatButton.scala | 18 +-
.../components/HelpModal.scala | 45 +-
.../components/LoginModal.scala | 20 +-
.../components/MainPanel.scala | 309 +++++++------
.../components/MetalsStatusIndicator.scala | 34 +-
.../components/MobileBar.scala | 47 +-
.../components/NewButton.scala | 29 +-
.../components/PrivacyPolicyModal.scala | 14 +-
.../components/PrivacyPolicyPrompt.scala | 41 +-
.../components/PromptModal.scala | 38 +-
.../components/RunButton.scala | 39 +-
.../components/ScaladexSearch.scala | 288 ++++++------
.../components/Scastie.scala | 315 +++++++------
.../components/SideBar.scala | 86 ++--
.../components/Status.scala | 58 ++-
.../components/TargetSelector.scala | 45 +-
.../components/TopBar.scala | 104 +++--
.../components/VersionSelector.scala | 119 ++---
.../components/ViewToggleButton.scala | 37 +-
.../components/WorksheetButton.scala | 35 +-
.../components/editor/CodeEditor.scala | 195 ++++----
.../editor/DebouncingCapabilities.scala | 19 +-
.../editor/DecorationProvider.scala | 95 ++--
.../components/editor/Editor.scala | 27 +-
.../components/editor/EditorKeymaps.scala | 79 ++--
.../components/editor/EditorTextOps.scala | 7 +-
.../editor/InteractiveProvider.scala | 39 +-
.../editor/MetalsAutocompletion.scala | 157 ++++---
.../components/editor/MetalsClient.scala | 74 ++--
.../components/editor/MetalsHover.scala | 44 +-
.../components/editor/OnChangeHandler.scala | 6 +-
.../components/editor/SimpleEditor.scala | 95 ++--
.../editor/SyntaxHighlightingHandler.scala | 40 +-
.../editor/SyntaxHighlightingPlugin.scala | 59 +--
.../editor/SyntaxHighlightingTheme.scala | 5 +-
.../components/editor/TreesitterParser.scala | 9 +-
.../components/package.scala | 92 ++--
.../com.olegych.scastie.client/package.scala | 10 +-
deployment/announce.scala | 46 +-
.../Instrument.scala | 112 +++--
.../InstrumentedInputs.scala | 30 +-
.../Patch.scala | 17 +-
.../Diff.scala | 39 +-
.../InstrumentSpecs.scala | 16 +-
project/CopyRecursively.scala | 15 +-
project/Deployment.scala | 311 ++++++-------
project/DockerHelper.scala | 32 +-
project/GenerateProjects.scala | 23 +-
.../SharedRuntime.scala | 2 +
.../package.scala | 4 +-
.../DomHook.scala | 7 +-
.../Runtime.scala | 28 +-
.../Runtime.scala | 9 +-
.../Runtime.scala | 8 +-
.../Runtime.scala | 11 +-
.../com.olegych.scastie.sbt/FormatActor.scala | 24 +-
.../OutputExtractor.scala | 85 ++--
.../com.olegych.scastie.sbt/SbtActor.scala | 54 +--
.../com.olegych.scastie.sbt/SbtMain.scala | 29 +-
.../com.olegych.scastie.sbt/SbtProcess.scala | 250 +++++------
.../FormatActorTest.scala | 6 +-
.../SbtActorTest.scala | 96 ++--
.../CompilerReporter.scala | 79 ++--
.../RuntimeErrorLogger.scala | 101 +++--
.../SbtScastiePlugin.scala | 32 +-
.../src/main/scala/sbt/ScastieTrapExit.scala | 316 +++++++------
.../PlayJsonSupport.scala | 81 ++--
.../RestApiServer.scala | 43 +-
.../com.olegych.scastie.web/ServerMain.scala | 14 +-
.../oauth2/Github.scala | 33 +-
.../oauth2/GithubUserSession.scala | 38 +-
.../oauth2/InMemoryRefreshTokenStorage.scala | 35 +-
.../oauth2/UserDirectives.scala | 17 +-
.../routes/ApiRoutes.scala | 128 +++---
.../routes/DownloadRoutes.scala | 36 +-
.../routes/FrontPageRoutes.scala | 88 ++--
.../routes/OAuth2Routes.scala | 117 +++--
.../routes/ProgressRoutes.scala | 22 +-
.../routes/ScalaJsRoutes.scala | 65 ++-
.../routes/ScalaLangRoutes.scala | 24 +-
.../routes/StatusRoutes.scala | 77 ++--
.../routes/package.scala | 61 ++-
.../SnippetIdMatcherTests.scala | 57 ++-
.../OldScastieConverter.scala | 91 ++--
.../SnippetsContainer.scala | 66 +--
.../filesystem/FilesystemContainer.scala | 3 +-
.../FilesystemSnippetsContainer.scala | 124 +++---
.../filesystem/FilesystemUsersContainer.scala | 30 +-
.../GenericFilesystemContainer.scala | 1 +
.../inmemory/InMemoryContainer.scala | 7 +-
.../inmemory/InMemorySnippetsContainer.scala | 42 +-
.../inmemory/InMemoryUsersContainer.scala | 7 +-
.../mongodb/GenericMongoContainer.scala | 1 +
.../mongodb/MongoDBContainer.scala | 7 +-
.../mongodb/MongoDBSnippetsContainer.scala | 43 +-
.../mongodb/MongoDBStoredClasses.scala | 28 +-
.../mongodb/MongoDBUsersContainer.scala | 11 +-
.../ContainerTest.scala | 120 ++---
.../com.olegych.scastie/util/Base64UUID.scala | 9 +-
.../util/BlockingProcess.scala | 241 +++++-----
.../util/GraphStageForwarder.scala | 10 +-
.../util/GraphStageLogicForwarder.scala | 29 +-
.../util/ProcessActor.scala | 65 ++-
.../util/ReconnectingActor.scala | 21 +-
.../com.olegych.scastie/util/SbtTask.scala | 3 +-
.../util/ScastieFileUtil.scala | 6 +-
.../ProcessActorTest.scala | 16 +-
158 files changed, 4666 insertions(+), 4848 deletions(-)
diff --git a/api/src/main/scala/com.olegych.scastie.api/ApiModels.scala b/api/src/main/scala/com.olegych.scastie.api/ApiModels.scala
index 2e7c3682b..9c4017ed6 100644
--- a/api/src/main/scala/com.olegych.scastie.api/ApiModels.scala
+++ b/api/src/main/scala/com.olegych.scastie.api/ApiModels.scala
@@ -9,39 +9,37 @@ case class SbtRunnerConnect(hostname: String, port: Int)
case object ActorConnected
object SnippetSummary {
- implicit val formatSnippetSummary: OFormat[SnippetSummary] =
- Json.format[SnippetSummary]
+ implicit val formatSnippetSummary: OFormat[SnippetSummary] = Json.format[SnippetSummary]
}
case class SnippetSummary(
- snippetId: SnippetId,
- summary: String,
- time: Long
+ snippetId: SnippetId,
+ summary: String,
+ time: Long
)
object FormatRequest {
- implicit val formatFormatRequest: OFormat[FormatRequest] =
- Json.format[FormatRequest]
+ implicit val formatFormatRequest: OFormat[FormatRequest] = Json.format[FormatRequest]
}
case class FormatRequest(
- code: String,
- isWorksheetMode: Boolean,
- scalaTarget: ScalaTarget
+ code: String,
+ isWorksheetMode: Boolean,
+ scalaTarget: ScalaTarget
)
object FormatResponse {
+
implicit object FormatResponseFormat extends Format[FormatResponse] {
+
def writes(response: FormatResponse): JsValue = {
response.result match {
- case Left(error) =>
- JsObject(
+ case Left(error) => JsObject(
Seq(
"Left" -> JsString(error)
)
)
- case Right(formatedCode) =>
- JsObject(
+ case Right(formatedCode) => JsObject(
Seq(
"Right" -> JsString(formatedCode)
)
@@ -51,51 +49,55 @@ object FormatResponse {
def reads(json: JsValue): JsResult[FormatResponse] = {
json match {
- case JsObject(v) =>
- v.toList match {
- case List(("Left", JsString(error))) =>
- JsSuccess(FormatResponse(Left(error)))
+ case JsObject(v) => v.toList match {
+ case List(("Left", JsString(error))) => JsSuccess(FormatResponse(Left(error)))
- case List(("Right", JsString(formatedCode))) =>
- JsSuccess(FormatResponse(Right(formatedCode)))
+ case List(("Right", JsString(formatedCode))) => JsSuccess(FormatResponse(Right(formatedCode)))
- case _ =>
- JsError(Seq())
+ case _ => JsError(Seq())
}
- case _ =>
- JsError(Seq())
+ case _ => JsError(Seq())
}
}
+
}
-}
+}
object EitherFormat {
import play.api.libs.functional.syntax._
+
implicit object JsEither {
- implicit def eitherReads[A, B](implicit A: Reads[A], B: Reads[B]): Reads[Either[A, B]] = {
+ implicit def eitherReads[A, B](
+ implicit A: Reads[A],
+ B: Reads[B]
+ ): Reads[Either[A, B]] = {
(JsPath \ "Left" \ "value").read[A].map(Left(_)) or
(JsPath \ "Right" \ "value").read[B].map(Right(_))
}
- implicit def eitherWrites[A, B](implicit A: Writes[A], B: Writes[B]): Writes[Either[A, B]] = Writes[Either[A, B]] {
+ implicit def eitherWrites[A, B](
+ implicit A: Writes[A],
+ B: Writes[B]
+ ): Writes[Either[A, B]] = Writes[Either[A, B]] {
case Left(value) => Json.obj("Left" -> Json.toJson(value))
case Right(value) => Json.obj("Right" -> Json.toJson(value))
}
- }
-}
+ }
+}
case class FormatResponse(
- result: Either[String, String]
+ result: Either[String, String]
)
object FetchResult {
implicit val formatFetchResult: OFormat[FetchResult] = Json.format[FetchResult]
- def create(inputs: Inputs, progresses: List[SnippetProgress]) = FetchResult(inputs, progresses.sortBy(p => (p.id, p.ts)))
+ def create(inputs: Inputs, progresses: List[SnippetProgress]) =
+ FetchResult(inputs, progresses.sortBy(p => (p.id, p.ts)))
}
case class FetchResult private (inputs: Inputs, progresses: List[SnippetProgress])
@@ -110,19 +112,17 @@ case class FetchScalaSource(snippetId: SnippetId)
case class FetchResultScalaSource(content: String)
object ScalaDependency {
- implicit val formatScalaDependency: OFormat[ScalaDependency] =
- Json.format[ScalaDependency]
+ implicit val formatScalaDependency: OFormat[ScalaDependency] = Json.format[ScalaDependency]
}
case class ScalaDependency(
- groupId: String,
- artifact: String,
- target: ScalaTarget,
- version: String
+ groupId: String,
+ artifact: String,
+ target: ScalaTarget,
+ version: String
) {
- def matches(sd: ScalaDependency): Boolean =
- sd.groupId == this.groupId &&
- sd.artifact == this.artifact
+ def matches(sd: ScalaDependency): Boolean = sd.groupId == this.groupId &&
+ sd.artifact == this.artifact
override def toString: String = target.renderSbt(this)
}
@@ -139,7 +139,7 @@ sealed trait FailureType {
val msg: String
}
-case class NoResult(msg: String) extends FailureType
+case class NoResult(msg: String) extends FailureType
case class PresentationCompilerFailure(msg: String) extends FailureType
object FailureType {
@@ -151,7 +151,8 @@ object NoResult {
}
object PresentationCompilerFailure {
- implicit val presentationCompilerFailureFormat: OFormat[PresentationCompilerFailure] = Json.format[PresentationCompilerFailure]
+ implicit val presentationCompilerFailureFormat: OFormat[PresentationCompilerFailure] =
+ Json.format[PresentationCompilerFailure]
}
object ScastieOffsetParams {
@@ -181,7 +182,6 @@ case class CompletionItemDTO(
symbol: Option[String]
)
-
case class HoverDTO(from: Int, to: Int, content: String)
case class CompletionsDTO(items: Set[CompletionItemDTO])
@@ -195,7 +195,8 @@ object InsertInstructions {
}
object AdditionalInsertInstructions {
- implicit val additionalInsertInstructionsFormat: OFormat[AdditionalInsertInstructions] = Json.format[AdditionalInsertInstructions]
+ implicit val additionalInsertInstructionsFormat: OFormat[AdditionalInsertInstructions] =
+ Json.format[AdditionalInsertInstructions]
}
object ScalaCompletionList {
@@ -219,15 +220,14 @@ object HoverDTO {
}
object Project {
- implicit val formatProject: OFormat[Project] =
- Json.format[Project]
+ implicit val formatProject: OFormat[Project] = Json.format[Project]
}
case class Project(
- organization: String,
- repository: String,
- logo: Option[String],
- artifacts: List[String]
+ organization: String,
+ repository: String,
+ logo: Option[String],
+ artifacts: List[String]
)
// Keep websocket connection
diff --git a/api/src/main/scala/com.olegych.scastie.api/CompilerInfo.scala b/api/src/main/scala/com.olegych.scastie.api/CompilerInfo.scala
index 3807457d0..5af129508 100644
--- a/api/src/main/scala/com.olegych.scastie.api/CompilerInfo.scala
+++ b/api/src/main/scala/com.olegych.scastie.api/CompilerInfo.scala
@@ -3,13 +3,14 @@ package com.olegych.scastie.api
import play.api.libs.json._
object Severity {
+
implicit object SeverityFormat extends Format[Severity] {
- def writes(severity: Severity): JsValue =
- severity match {
- case Info => JsString("Info")
- case Warning => JsString("Warning")
- case Error => JsString("Error")
- }
+
+ def writes(severity: Severity): JsValue = severity match {
+ case Info => JsString("Info")
+ case Warning => JsString("Warning")
+ case Error => JsString("Error")
+ }
def reads(json: JsValue): JsResult[Severity] = {
json match {
@@ -19,20 +20,22 @@ object Severity {
case _ => JsError(Seq())
}
}
+
}
+
}
sealed trait Severity
-case object Info extends Severity
+case object Info extends Severity
case object Warning extends Severity
-case object Error extends Severity
+case object Error extends Severity
object Problem {
implicit val formatProblem: OFormat[Problem] = Json.format[Problem]
}
case class Problem(
- severity: Severity,
- line: Option[Int],
- message: String
+ severity: Severity,
+ line: Option[Int],
+ message: String
)
diff --git a/api/src/main/scala/com.olegych.scastie.api/ConsoleOutput.scala b/api/src/main/scala/com.olegych.scastie.api/ConsoleOutput.scala
index ffa500676..c78656a2e 100644
--- a/api/src/main/scala/com.olegych.scastie.api/ConsoleOutput.scala
+++ b/api/src/main/scala/com.olegych.scastie.api/ConsoleOutput.scala
@@ -8,6 +8,7 @@ sealed trait ConsoleOutput {
}
object ConsoleOutput {
+
case class SbtOutput(output: ProcessOutput) extends ConsoleOutput {
def show: String = s"sbt: ${output.line}"
}
@@ -22,13 +23,12 @@ object ConsoleOutput {
implicit object ConsoleOutputFormat extends Format[ConsoleOutput] {
val formatSbtOutput: OFormat[SbtOutput] = Json.format[SbtOutput]
- private val formatUserOutput = Json.format[UserOutput]
- private val formatScastieOutput = Json.format[ScastieOutput]
+ private val formatUserOutput = Json.format[UserOutput]
+ private val formatScastieOutput = Json.format[ScastieOutput]
def writes(output: ConsoleOutput): JsValue = {
output match {
- case sbtOutput: SbtOutput =>
- formatSbtOutput.writes(sbtOutput) ++ JsObject(Seq("tpe" -> JsString("SbtOutput")))
+ case sbtOutput: SbtOutput => formatSbtOutput.writes(sbtOutput) ++ JsObject(Seq("tpe" -> JsString("SbtOutput")))
case userOutput: UserOutput =>
formatUserOutput.writes(userOutput) ++ JsObject(Seq("tpe" -> JsString("UserOutput")))
case scastieOutput: ScastieOutput =>
@@ -41,18 +41,18 @@ object ConsoleOutput {
case obj: JsObject =>
val vs = obj.value
vs.get("tpe").orElse(vs.get("$type")) match {
- case Some(tpe) =>
- tpe match {
- case JsString("SbtOutput") => formatSbtOutput.reads(json)
- case JsString("UserOutput") => formatUserOutput.reads(json)
- case JsString("ScastieOutput") =>
- formatScastieOutput.reads(json)
- case _ => JsError(Seq())
+ case Some(tpe) => tpe match {
+ case JsString("SbtOutput") => formatSbtOutput.reads(json)
+ case JsString("UserOutput") => formatUserOutput.reads(json)
+ case JsString("ScastieOutput") => formatScastieOutput.reads(json)
+ case _ => JsError(Seq())
}
case None => JsError(Seq())
}
case _ => JsError(Seq())
}
}
+
}
+
}
diff --git a/api/src/main/scala/com.olegych.scastie.api/Inputs.scala b/api/src/main/scala/com.olegych.scastie.api/Inputs.scala
index 1bba80b7f..2b11ab1b1 100644
--- a/api/src/main/scala/com.olegych.scastie.api/Inputs.scala
+++ b/api/src/main/scala/com.olegych.scastie.api/Inputs.scala
@@ -1,8 +1,7 @@
package com.olegych.scastie.api
-import play.api.libs.json._
import com.olegych.scastie.buildinfo.BuildInfo
-
+import play.api.libs.json._
import System.{lineSeparator => nl}
sealed trait BaseInputs {
@@ -56,22 +55,23 @@ object Inputs {
f
)
}
+
}
case class Inputs(
- _isWorksheetMode: Boolean,
- code: String,
- target: ScalaTarget,
- libraries: Set[ScalaDependency],
- librariesFromList: List[(ScalaDependency, Project)],
- sbtConfigExtra: String,
- sbtConfigSaved: Option[String],
- sbtPluginsConfigExtra: String,
- sbtPluginsConfigSaved: Option[String],
- isShowingInUserProfile: Boolean,
- forked: Option[SnippetId] = None
+ _isWorksheetMode: Boolean,
+ code: String,
+ target: ScalaTarget,
+ libraries: Set[ScalaDependency],
+ librariesFromList: List[(ScalaDependency, Project)],
+ sbtConfigExtra: String,
+ sbtConfigSaved: Option[String],
+ sbtPluginsConfigExtra: String,
+ sbtPluginsConfigSaved: Option[String],
+ isShowingInUserProfile: Boolean,
+ forked: Option[SnippetId] = None
) extends BaseInputs {
- val isWorksheetMode = _isWorksheetMode && target.hasWorksheetMode
+ val isWorksheetMode = _isWorksheetMode && target.hasWorksheetMode
val librariesFrom: Map[ScalaDependency, Project] = librariesFromList.toMap
private lazy val sbtInputs: (String, String) = (sbtConfig, sbtPluginsConfig)
@@ -111,9 +111,11 @@ case class Inputs(
lazy val isDefault: Boolean = copy(code = "").withSavedConfig == Inputs.default.copy(code = "").withSavedConfig
- def modifyConfig(inputs: Inputs => Inputs): Inputs = inputs(this).copy(sbtConfigSaved = None, sbtPluginsConfigSaved = None)
+ def modifyConfig(inputs: Inputs => Inputs): Inputs =
+ inputs(this).copy(sbtConfigSaved = None, sbtPluginsConfigSaved = None)
- def withSavedConfig: Inputs = copy(sbtConfigSaved = Some(sbtConfigGenerated), sbtPluginsConfigSaved = Some(sbtPluginsConfigGenerated))
+ def withSavedConfig: Inputs =
+ copy(sbtConfigSaved = Some(sbtConfigGenerated), sbtPluginsConfigSaved = Some(sbtPluginsConfigGenerated))
def clearDependencies: Inputs = {
modifyConfig {
@@ -144,11 +146,10 @@ case class Inputs(
def updateScalaDependency(scalaDependency: ScalaDependency, version: String): Inputs = {
val newScalaDependency = scalaDependency.copy(version = version)
- val newLibraries = libraries.filterNot(_.matches(scalaDependency)) + newScalaDependency
+ val newLibraries = libraries.filterNot(_.matches(scalaDependency)) + newScalaDependency
val newLibrariesFromList = librariesFromList.collect {
- case (l, p) if l.matches(scalaDependency) =>
- newScalaDependency -> p
- case (l, p) => l -> p
+ case (l, p) if l.matches(scalaDependency) => newScalaDependency -> p
+ case (l, p) => l -> p
}
modifyConfig {
_.copy(
@@ -158,8 +159,7 @@ case class Inputs(
}
}
- lazy val sbtConfig: String =
- mapToConfig(sbtConfigGenerated, sbtConfigExtra)
+ lazy val sbtConfig: String = mapToConfig(sbtConfigGenerated, sbtConfigExtra)
lazy val sbtConfigGenerated: String = sbtConfigSaved.getOrElse {
val targetConfig = target.sbtConfig
@@ -168,15 +168,14 @@ case class Inputs(
if (target.hasWorksheetMode) target.runtimeDependency
else None
- val allLibraries =
- optionalTargetDependency.map(libraries + _).getOrElse(libraries)
+ val allLibraries = optionalTargetDependency.map(libraries + _).getOrElse(libraries)
val librariesConfig =
if (allLibraries.isEmpty) ""
else if (allLibraries.size == 1) {
s"libraryDependencies += " + target.renderSbt(allLibraries.head)
} else {
- val nl = "\n"
+ val nl = "\n"
val tab = " "
"libraryDependencies ++= " +
allLibraries
@@ -191,31 +190,28 @@ case class Inputs(
mapToConfig(targetConfig, librariesConfig)
}
- lazy val sbtPluginsConfig: String =
- mapToConfig(sbtPluginsConfigGenerated, sbtPluginsConfigExtra)
+ lazy val sbtPluginsConfig: String = mapToConfig(sbtPluginsConfigGenerated, sbtPluginsConfigExtra)
lazy val sbtPluginsConfigGenerated: String = sbtPluginsConfigSaved.getOrElse {
sbtPluginsConfig0(withSbtScastie = true)
}
- private def mapToConfig(parts: String*): String =
- parts.filter(_.nonEmpty).mkString("\n")
+ private def mapToConfig(parts: String*): String = parts.filter(_.nonEmpty).mkString("\n")
private def sbtPluginsConfig0(withSbtScastie: Boolean): String = {
val targetConfig = target.sbtPluginsConfig
val sbtScastie =
- if (withSbtScastie)
- s"""addSbtPlugin("org.scastie" % "sbt-scastie" % "${BuildInfo.versionRuntime}")"""
+ if (withSbtScastie) s"""addSbtPlugin("org.scastie" % "sbt-scastie" % "${BuildInfo.versionRuntime}")"""
else ""
mapToConfig(targetConfig, sbtScastie)
}
+
}
object EditInputs {
- implicit val formatEditInputs: OFormat[EditInputs] =
- Json.format[EditInputs]
+ implicit val formatEditInputs: OFormat[EditInputs] = Json.format[EditInputs]
}
case class EditInputs(snippetId: SnippetId, inputs: Inputs)
diff --git a/api/src/main/scala/com.olegych.scastie.api/Instrumentation.scala b/api/src/main/scala/com.olegych.scastie.api/Instrumentation.scala
index 1176c018b..31543aef3 100644
--- a/api/src/main/scala/com.olegych.scastie.api/Instrumentation.scala
+++ b/api/src/main/scala/com.olegych.scastie.api/Instrumentation.scala
@@ -3,19 +3,17 @@ package com.olegych.scastie.api
import play.api.libs.json._
object Render {
+
implicit object RenderFormat extends Format[Render] {
- private val formatValue = Json.format[Value]
- private val formatHtml = Json.format[Html]
+ private val formatValue = Json.format[Value]
+ private val formatHtml = Json.format[Html]
private val formatAttachedDom = Json.format[AttachedDom]
def writes(render: Render): JsValue = {
render match {
- case v: Value =>
- formatValue.writes(v) ++ JsObject(Seq("tpe" -> JsString("Value")))
- case h: Html =>
- formatHtml.writes(h) ++ JsObject(Seq("tpe" -> JsString("Html")))
- case a: AttachedDom =>
- formatAttachedDom.writes(a) ++ JsObject(Seq("tpe" -> JsString("AttachedDom")))
+ case v: Value => formatValue.writes(v) ++ JsObject(Seq("tpe" -> JsString("Value")))
+ case h: Html => formatHtml.writes(h) ++ JsObject(Seq("tpe" -> JsString("Html")))
+ case a: AttachedDom => formatAttachedDom.writes(a) ++ JsObject(Seq("tpe" -> JsString("AttachedDom")))
}
}
@@ -24,8 +22,7 @@ object Render {
case obj: JsObject =>
val vs = obj.value
vs.get("tpe").orElse(vs.get("$type")) match {
- case Some(tpe) =>
- tpe match {
+ case Some(tpe) => tpe match {
case JsString("Value") => formatValue.reads(json)
case JsString("Html") => formatHtml.reads(json)
case JsString("AttachedDom") => formatAttachedDom.reads(json)
@@ -36,26 +33,29 @@ object Render {
case _ => JsError(Seq())
}
}
+
}
+
}
sealed trait Render
case class Value(v: String, className: String) extends Render
+
case class Html(a: String, folded: Boolean = false) extends Render {
def stripMargin: Html = copy(a = a.stripMargin)
- def fold: Html = copy(folded = true)
+ def fold: Html = copy(folded = true)
}
+
case class AttachedDom(uuid: String, folded: Boolean = false) extends Render {
def fold: AttachedDom = copy(folded = true)
}
object Instrumentation {
- val instrumentedObject = "Playground"
- implicit val formatInstrumentation: OFormat[Instrumentation] =
- Json.format[Instrumentation]
+ val instrumentedObject = "Playground"
+ implicit val formatInstrumentation: OFormat[Instrumentation] = Json.format[Instrumentation]
}
case class Instrumentation(
- position: Position,
- render: Render
+ position: Position,
+ render: Render
)
diff --git a/api/src/main/scala/com.olegych.scastie.api/Outputs.scala b/api/src/main/scala/com.olegych.scastie.api/Outputs.scala
index 6db35c81a..30c2c1b04 100644
--- a/api/src/main/scala/com.olegych.scastie.api/Outputs.scala
+++ b/api/src/main/scala/com.olegych.scastie.api/Outputs.scala
@@ -3,8 +3,7 @@ package com.olegych.scastie.api
import play.api.libs.json._
object ReleaseOptions {
- implicit val formatReleaseOptions: OFormat[ReleaseOptions] =
- Json.format[ReleaseOptions]
+ implicit val formatReleaseOptions: OFormat[ReleaseOptions] = Json.format[ReleaseOptions]
}
case class ReleaseOptions(groupId: String, versions: List[String], version: String)
@@ -12,8 +11,7 @@ case class ReleaseOptions(groupId: String, versions: List[String], version: Stri
// case class MavenReference(groupId: String, artifactId: String, version: String)
object Outputs {
- implicit val formatOutputs: OFormat[Outputs] =
- Json.format[Outputs]
+ implicit val formatOutputs: OFormat[Outputs] = Json.format[Outputs]
def default: Outputs = Outputs(
consoleOutputs = Vector(),
@@ -22,27 +20,28 @@ object Outputs {
runtimeError = None,
sbtError = false
)
+
}
+
case class Outputs(
- consoleOutputs: Vector[ConsoleOutput],
- compilationInfos: Set[Problem],
- instrumentations: Set[Instrumentation],
- runtimeError: Option[RuntimeError],
- sbtError: Boolean
+ consoleOutputs: Vector[ConsoleOutput],
+ compilationInfos: Set[Problem],
+ instrumentations: Set[Instrumentation],
+ runtimeError: Option[RuntimeError],
+ sbtError: Boolean
) {
def console: String = consoleOutputs.map(_.show).mkString("\n")
- def isClearable: Boolean =
- consoleOutputs.nonEmpty ||
- compilationInfos.nonEmpty ||
- instrumentations.nonEmpty ||
- runtimeError.isDefined
+ def isClearable: Boolean = consoleOutputs.nonEmpty ||
+ compilationInfos.nonEmpty ||
+ instrumentations.nonEmpty ||
+ runtimeError.isDefined
+
}
object Position {
- implicit val formatPosition: OFormat[Position] =
- Json.format[Position]
+ implicit val formatPosition: OFormat[Position] = Json.format[Position]
}
case class Position(start: Int, end: Int)
diff --git a/api/src/main/scala/com.olegych.scastie.api/ProcessOutput.scala b/api/src/main/scala/com.olegych.scastie.api/ProcessOutput.scala
index b0a61a063..93f3ec381 100644
--- a/api/src/main/scala/com.olegych.scastie.api/ProcessOutput.scala
+++ b/api/src/main/scala/com.olegych.scastie.api/ProcessOutput.scala
@@ -3,20 +3,21 @@ package com.olegych.scastie.api
import play.api.libs.json._
trait ProcessOutputType
+
object ProcessOutputType {
case object StdOut extends ProcessOutputType
case object StdErr extends ProcessOutputType
implicit object ProcessOutputTypeFormat extends Format[ProcessOutputType] {
+
def writes(processOutputType: ProcessOutputType): JsValue = {
JsString(processOutputType.toString)
}
- private val values =
- List(
- StdOut,
- StdErr
- ).map(v => (v.toString, v)).toMap
+ private val values = List(
+ StdOut,
+ StdErr
+ ).map(v => (v.toString, v)).toMap
def reads(json: JsValue): JsResult[ProcessOutputType] = {
json match {
@@ -29,7 +30,9 @@ object ProcessOutputType {
case _ => JsError(Seq())
}
}
+
}
+
}
object ProcessOutput {
@@ -37,7 +40,7 @@ object ProcessOutput {
}
case class ProcessOutput(
- line: String,
- tpe: ProcessOutputType,
- id: Option[Long]
+ line: String,
+ tpe: ProcessOutputType,
+ id: Option[Long]
)
diff --git a/api/src/main/scala/com.olegych.scastie.api/RuntimeError.scala b/api/src/main/scala/com.olegych.scastie.api/RuntimeError.scala
index 89b128cd2..714c7a47f 100644
--- a/api/src/main/scala/com.olegych.scastie.api/RuntimeError.scala
+++ b/api/src/main/scala/com.olegych.scastie.api/RuntimeError.scala
@@ -1,35 +1,32 @@
package com.olegych.scastie.api
import java.io.{PrintWriter, StringWriter}
+
import play.api.libs.json._
case class RuntimeError(
- message: String,
- line: Option[Int],
- fullStack: String
+ message: String,
+ line: Option[Int],
+ fullStack: String
)
object RuntimeError {
- implicit val formatRuntimeError: OFormat[RuntimeError] =
- Json.format[RuntimeError]
+ implicit val formatRuntimeError: OFormat[RuntimeError] = Json.format[RuntimeError]
def wrap[T](in: => T): Either[Option[RuntimeError], T] = {
try {
Right(in)
} catch {
- case ex: Exception =>
- Left(RuntimeError.fromThrowable(ex, fromScala = false))
+ case ex: Exception => Left(RuntimeError.fromThrowable(ex, fromScala = false))
}
}
def fromThrowable(t: Throwable, fromScala: Boolean = true): Option[RuntimeError] = {
def search(e: Throwable) = {
e.getStackTrace
- .find(
- trace =>
- if (fromScala)
- trace.getFileName == "main.scala" && trace.getLineNumber != -1
- else true
+ .find(trace =>
+ if (fromScala) trace.getFileName == "main.scala" && trace.getLineNumber != -1
+ else true
)
.map(v => (e, Some(v.getLineNumber)))
}
@@ -41,20 +38,19 @@ object RuntimeError {
else s
}
- loop(t).map {
- case (err, line) =>
- val errors = new StringWriter()
- t.printStackTrace(new PrintWriter(errors))
- val fullStack = errors.toString
+ loop(t).map { case (err, line) =>
+ val errors = new StringWriter()
+ t.printStackTrace(new PrintWriter(errors))
+ val fullStack = errors.toString
- RuntimeError(err.toString, line, fullStack)
+ RuntimeError(err.toString, line, fullStack)
}
}
+
}
object RuntimeErrorWrap {
- implicit val formatRuntimeErrorWrap: OFormat[RuntimeErrorWrap] =
- Json.format[RuntimeErrorWrap]
+ implicit val formatRuntimeErrorWrap: OFormat[RuntimeErrorWrap] = Json.format[RuntimeErrorWrap]
}
case class RuntimeErrorWrap(error: Option[RuntimeError])
diff --git a/api/src/main/scala/com.olegych.scastie.api/SbtState.scala b/api/src/main/scala/com.olegych.scastie.api/SbtState.scala
index a6ca61c31..99317996d 100644
--- a/api/src/main/scala/com.olegych.scastie.api/SbtState.scala
+++ b/api/src/main/scala/com.olegych.scastie.api/SbtState.scala
@@ -3,27 +3,29 @@ package com.olegych.scastie.api
import play.api.libs.json._
sealed trait SbtState extends ServerState
+
object SbtState {
+
case object Unknown extends SbtState {
override def toString: String = "Unknown"
- def isReady: Boolean = true
+ def isReady: Boolean = true
}
case object Disconnected extends SbtState {
override def toString: String = "Disconnected"
- def isReady: Boolean = false
+ def isReady: Boolean = false
}
implicit object SbtStateFormat extends Format[SbtState] {
+
def writes(state: SbtState): JsValue = {
JsString(state.toString)
}
- private val values =
- List(
- Unknown,
- Disconnected
- ).map(v => (v.toString, v)).toMap
+ private val values = List(
+ Unknown,
+ Disconnected
+ ).map(v => (v.toString, v)).toMap
def reads(json: JsValue): JsResult[SbtState] = {
json match {
@@ -36,5 +38,7 @@ object SbtState {
case _ => JsError(Seq())
}
}
+
}
+
}
diff --git a/api/src/main/scala/com.olegych.scastie.api/ScalaJsResult.scala b/api/src/main/scala/com.olegych.scastie.api/ScalaJsResult.scala
index cb9658d07..6856bd488 100644
--- a/api/src/main/scala/com.olegych.scastie.api/ScalaJsResult.scala
+++ b/api/src/main/scala/com.olegych.scastie.api/ScalaJsResult.scala
@@ -3,7 +3,7 @@ package com.olegych.scastie.api
import play.api.libs.json._
case class ScalaJsResult(
- in: Either[Option[RuntimeError], List[Instrumentation]]
+ in: Either[Option[RuntimeError], List[Instrumentation]]
)
object ScalaJsResult {
@@ -11,15 +11,13 @@ object ScalaJsResult {
private case class Instrumentations(instrs: List[Instrumentation])
implicit object ScalaJsResultFormat extends Format[ScalaJsResult] {
- private val formatLeft = Json.format[Error]
+ private val formatLeft = Json.format[Error]
private val formatRight = Json.format[Instrumentations]
def writes(result: ScalaJsResult): JsValue = {
result.in match {
- case Left(err) =>
- formatLeft.writes(Error(err)) ++ JsObject(Seq("tpe" -> JsString("Left")))
- case Right(instrs) =>
- formatRight.writes(Instrumentations(instrs)) ++ JsObject(Seq("tpe" -> JsString("Right")))
+ case Left(err) => formatLeft.writes(Error(err)) ++ JsObject(Seq("tpe" -> JsString("Left")))
+ case Right(instrs) => formatRight.writes(Instrumentations(instrs)) ++ JsObject(Seq("tpe" -> JsString("Right")))
}
}
@@ -28,12 +26,9 @@ object ScalaJsResult {
case obj: JsObject =>
val vs = obj.value
vs.get("tpe").orElse(vs.get("$type")) match {
- case Some(JsString(tpe)) =>
- tpe match {
- case "Left" =>
- formatLeft.reads(json).map(v => ScalaJsResult(Left(v.er)))
- case "Right" =>
- formatRight
+ case Some(JsString(tpe)) => tpe match {
+ case "Left" => formatLeft.reads(json).map(v => ScalaJsResult(Left(v.er)))
+ case "Right" => formatRight
.reads(json)
.map(v => ScalaJsResult(Right(v.instrs)))
case _ => JsError(Seq())
@@ -43,5 +38,7 @@ object ScalaJsResult {
case _ => JsError(Seq())
}
}
+
}
+
}
diff --git a/api/src/main/scala/com.olegych.scastie.api/ScalaTarget.scala b/api/src/main/scala/com.olegych.scastie.api/ScalaTarget.scala
index f8035eed8..d7dcdcb5a 100644
--- a/api/src/main/scala/com.olegych.scastie.api/ScalaTarget.scala
+++ b/api/src/main/scala/com.olegych.scastie.api/ScalaTarget.scala
@@ -11,13 +11,11 @@ sealed trait ScalaTarget {
def sbtPluginsConfig: String
def sbtRunCommand(worksheetMode: Boolean): String
- def runtimeDependency: Option[ScalaDependency] =
- ScalaTarget.runtimeDependencyFrom(this)
+ def runtimeDependency: Option[ScalaDependency] = ScalaTarget.runtimeDependencyFrom(this)
def hasWorksheetMode: Boolean = true
- protected def sbtConfigScalaVersion: String =
- s"""scalaVersion := "$scalaVersion""""
+ protected def sbtConfigScalaVersion: String = s"""scalaVersion := "$scalaVersion""""
protected def renderSbtDouble(lib: ScalaDependency): String = {
import lib._
@@ -42,10 +40,11 @@ object ScalaTarget {
import play.api.libs.json._
implicit object ScalaTargetFormat extends Format[ScalaTarget] {
- private val formatJvm = Json.format[Jvm]
- private val formatJs = Json.format[Js]
+ private val formatJvm = Json.format[Jvm]
+ private val formatJs = Json.format[Js]
private val formatTypelevel = Json.format[Typelevel]
- private val formatNative = Json.format[Native]
+ private val formatNative = Json.format[Native]
+
private val formatScala3: OFormat[Scala3] = {
// Scala3.dottyVersion has been renamed to Scala3.scalaVersion
// so we use the default write
@@ -66,16 +65,11 @@ object ScalaTarget {
def writes(target: ScalaTarget): JsValue = {
target match {
- case jvm: Jvm =>
- formatJvm.writes(jvm) ++ JsObject(Seq("tpe" -> JsString("Jvm")))
- case js: Js =>
- formatJs.writes(js) ++ JsObject(Seq("tpe" -> JsString("Js")))
- case typelevel: Typelevel =>
- formatTypelevel.writes(typelevel) ++ JsObject(Seq("tpe" -> JsString("Typelevel")))
- case native: Native =>
- formatNative.writes(native) ++ JsObject(Seq("tpe" -> JsString("Native")))
- case dotty: Scala3 =>
- formatScala3.writes(dotty) ++ JsObject(Seq("tpe" -> JsString("Scala3")))
+ case jvm: Jvm => formatJvm.writes(jvm) ++ JsObject(Seq("tpe" -> JsString("Jvm")))
+ case js: Js => formatJs.writes(js) ++ JsObject(Seq("tpe" -> JsString("Js")))
+ case typelevel: Typelevel => formatTypelevel.writes(typelevel) ++ JsObject(Seq("tpe" -> JsString("Typelevel")))
+ case native: Native => formatNative.writes(native) ++ JsObject(Seq("tpe" -> JsString("Native")))
+ case dotty: Scala3 => formatScala3.writes(dotty) ++ JsObject(Seq("tpe" -> JsString("Scala3")))
}
}
@@ -84,8 +78,7 @@ object ScalaTarget {
case obj: JsObject =>
val vs = obj.value
vs.get("tpe").orElse(vs.get("$type")) match {
- case Some(JsString(tpe)) =>
- tpe match {
+ case Some(JsString(tpe)) => tpe match {
case "Jvm" => formatJvm.reads(json)
case "Js" => formatJs.reads(json)
case "Typelevel" => formatTypelevel.reads(json)
@@ -98,6 +91,7 @@ object ScalaTarget {
case _ => JsError(Seq())
}
}
+
}
private def runtimeDependencyFrom(target: ScalaTarget): Option[ScalaDependency] = Some(
@@ -114,6 +108,7 @@ object ScalaTarget {
}
private def partialUnificationSbtPlugin = """addSbtPlugin("org.lyranthe.sbt" % "partial-unification" % "1.1.2")"""
+
private def hktScalacOptions(scalaVersion: String) = {
val (kpOrg, kpVersion, kpCross) =
if (scalaVersion == "2.13.0-M5") ("org.spire-math", "0.9.9", "binary")
@@ -133,19 +128,16 @@ object ScalaTarget {
case class Jvm(scalaVersion: String) extends ScalaTarget {
- def targetType: ScalaTargetType =
- ScalaTargetType.Scala2
+ def targetType: ScalaTargetType = ScalaTargetType.Scala2
- def scaladexRequest: Map[String, String] =
- Map("target" -> "JVM", "scalaVersion" -> binaryScalaVersion)
+ def scaladexRequest: Map[String, String] = Map("target" -> "JVM", "scalaVersion" -> binaryScalaVersion)
- def renderSbt(lib: ScalaDependency): String =
- renderSbtDouble(lib)
+ def renderSbt(lib: ScalaDependency): String = renderSbtDouble(lib)
def sbtConfig: String = {
val base = sbtConfigScalaVersion + "\n" + hktScalacOptions(scalaVersion)
if (scalaVersion.startsWith("2.13") || scalaVersion.startsWith("2.12"))
- base + "\n" + "scalacOptions += \"-Ydelambdafy:inline\"" //workaround https://github.com/scala/bug/issues/10782
+ base + "\n" + "scalacOptions += \"-Ydelambdafy:inline\"" // workaround https://github.com/scala/bug/issues/10782
else base
}
@@ -157,20 +149,16 @@ object ScalaTarget {
}
object Typelevel {
- def default: ScalaTarget =
- ScalaTarget.Typelevel(scalaVersion = "2.12.3-bin-typelevel-4")
+ def default: ScalaTarget = ScalaTarget.Typelevel(scalaVersion = "2.12.3-bin-typelevel-4")
}
case class Typelevel(scalaVersion: String) extends ScalaTarget {
- def targetType: ScalaTargetType =
- ScalaTargetType.Typelevel
+ def targetType: ScalaTargetType = ScalaTargetType.Typelevel
- def scaladexRequest: Map[String, String] =
- Map("target" -> "JVM", "scalaVersion" -> scalaVersion)
+ def scaladexRequest: Map[String, String] = Map("target" -> "JVM", "scalaVersion" -> scalaVersion)
- def renderSbt(lib: ScalaDependency): String =
- renderSbtDouble(lib)
+ def renderSbt(lib: ScalaDependency): String = renderSbtDouble(lib)
def sbtConfig: String = {
s"""|$sbtConfigScalaVersion
@@ -185,31 +173,30 @@ object ScalaTarget {
}
object Js {
- val targetFilename = "fastopt.js"
+ val targetFilename = "fastopt.js"
val sourceMapFilename: String = targetFilename + ".map"
- val sourceFilename = "main.scala"
- val sourceUUID = "file:///tmp/LxvjvKARSa2U5ctNis9LIA"
+ val sourceFilename = "main.scala"
+ val sourceUUID = "file:///tmp/LxvjvKARSa2U5ctNis9LIA"
def default = ScalaTarget.Js(
scalaVersion = BuildInfo.jsScalaVersion,
scalaJsVersion = BuildInfo.defaultScalaJsVersion
)
+
}
case class Js(scalaVersion: String, scalaJsVersion: String) extends ScalaTarget {
- def targetType: ScalaTargetType =
- ScalaTargetType.JS
+ def targetType: ScalaTargetType = ScalaTargetType.JS
def scaladexRequest: Map[String, String] = Map(
- "target" -> "JS",
+ "target" -> "JS",
"scalaVersion" -> binaryScalaVersion,
"scalaJsVersion" -> (if (scalaJsVersion.startsWith("0.")) scalaJsVersion.split('.').init.mkString(".")
else scalaJsVersion.split('.').head)
)
- def renderSbt(lib: ScalaDependency): String =
- s"${renderSbtCross(lib)} cross CrossVersion.for3Use2_13"
+ def renderSbt(lib: ScalaDependency): String = s"${renderSbtCross(lib)} cross CrossVersion.for3Use2_13"
def sbtConfig: String = {
s"""|$sbtConfigScalaVersion
@@ -219,13 +206,13 @@ object ScalaTarget {
|scalacOptions += {
| val from = (LocalRootProject / baseDirectory).value.toURI.toString
| val to = "${ScalaTarget.Js.sourceUUID}/"
- | "-${if (scalaVersion.startsWith("3")) "scalajs-mapSourceURI" else "P:scalajs:mapSourceURI"}:" + from + "->" + to
+ | "-${if (scalaVersion.startsWith("3")) "scalajs-mapSourceURI"
+ else "P:scalajs:mapSourceURI"}:" + from + "->" + to
|}""".stripMargin
}
- def sbtPluginsConfig: String =
- s"""addSbtPlugin("org.scala-js" % "sbt-scalajs" % "$scalaJsVersion")""" + "\n" +
- (if (!scalaVersion.startsWith("3")) partialUnificationSbtPlugin else "")
+ def sbtPluginsConfig: String = s"""addSbtPlugin("org.scala-js" % "sbt-scalajs" % "$scalaJsVersion")""" + "\n" +
+ (if (!scalaVersion.startsWith("3")) partialUnificationSbtPlugin else "")
def sbtRunCommand(worksheetMode: Boolean): String = "fastOptJS"
@@ -233,55 +220,49 @@ object ScalaTarget {
}
object Native {
- def default: Native =
- ScalaTarget.Native(
- scalaVersion = "2.11.11",
- scalaNativeVersion = "0.3.3"
- )
+
+ def default: Native = ScalaTarget.Native(
+ scalaVersion = "2.11.11",
+ scalaNativeVersion = "0.3.3"
+ )
+
}
case class Native(scalaVersion: String, scalaNativeVersion: String) extends ScalaTarget {
- def targetType: ScalaTargetType =
- ScalaTargetType.Native
+ def targetType: ScalaTargetType = ScalaTargetType.Native
- def scaladexRequest: Map[String, String] =
- Map(
- "target" -> "NATIVE",
- "scalaVersion" -> binaryScalaVersion,
- "scalaNativeVersion" -> scalaNativeVersion
- )
+ def scaladexRequest: Map[String, String] = Map(
+ "target" -> "NATIVE",
+ "scalaVersion" -> binaryScalaVersion,
+ "scalaNativeVersion" -> scalaNativeVersion
+ )
- def renderSbt(lib: ScalaDependency): String =
- renderSbtCross(lib)
+ def renderSbt(lib: ScalaDependency): String = renderSbtCross(lib)
def sbtConfig: String = sbtConfigScalaVersion
- def sbtPluginsConfig: String =
- s"""addSbtPlugin("org.scala-native" % "sbt-scala-native" % "$scalaNativeVersion")"""
+ def sbtPluginsConfig: String = s"""addSbtPlugin("org.scala-native" % "sbt-scala-native" % "$scalaNativeVersion")"""
def sbtRunCommand(worksheetMode: Boolean): String = if (worksheetMode) "fgRunMain Main" else "fgRun"
- override def toString: String =
- s"Scala-Native $scalaVersion $scalaNativeVersion"
+ override def toString: String = s"Scala-Native $scalaVersion $scalaNativeVersion"
}
object Scala3 {
def default: ScalaTarget = Scala3(BuildInfo.stableLTS)
- def defaultCode: String =
- """|// You can find more examples here:
- |// https://github.com/lampepfl/dotty-example-project
- |println("Hi Scala 3!")
- |""".stripMargin
+ def defaultCode: String = """|// You can find more examples here:
+ |// https://github.com/lampepfl/dotty-example-project
+ |println("Hi Scala 3!")
+ |""".stripMargin
+
}
case class Scala3(scalaVersion: String) extends ScalaTarget {
- def targetType: ScalaTargetType =
- ScalaTargetType.Scala3
+ def targetType: ScalaTargetType = ScalaTargetType.Scala3
- def scaladexRequest: Map[String, String] =
- Map("target" -> "JVM", "scalaVersion" -> binaryScalaVersion)
+ def scaladexRequest: Map[String, String] = Map("target" -> "JVM", "scalaVersion" -> binaryScalaVersion)
def renderSbt(lib: ScalaDependency): String = {
if (Some(lib) == runtimeDependency) renderSbtDouble(lib)
@@ -296,7 +277,7 @@ object ScalaTarget {
def sbtRunCommand(worksheetMode: Boolean): String = if (worksheetMode) "fgRunMain Main" else "fgRun"
- override def toString: String =
- s"Scala $scalaVersion"
+ override def toString: String = s"Scala $scalaVersion"
}
+
}
diff --git a/api/src/main/scala/com.olegych.scastie.api/ScalaTargetType.scala b/api/src/main/scala/com.olegych.scastie.api/ScalaTargetType.scala
index 7bc640e9e..17a3ee513 100644
--- a/api/src/main/scala/com.olegych.scastie.api/ScalaTargetType.scala
+++ b/api/src/main/scala/com.olegych.scastie.api/ScalaTargetType.scala
@@ -20,18 +20,18 @@ object ScalaTargetType {
}
implicit object ScalaTargetTypeFormat extends Format[ScalaTargetType] {
+
def writes(scalaTargetType: ScalaTargetType): JsValue = {
JsString(scalaTargetType.toString)
}
- private val values =
- List(
- Scala2,
- Scala3,
- JS,
- Native,
- Typelevel
- ).map(v => (v.toString, v)).toMap
+ private val values = List(
+ Scala2,
+ Scala3,
+ JS,
+ Native,
+ Typelevel
+ ).map(v => (v.toString, v)).toMap
def reads(json: JsValue): JsResult[ScalaTargetType] = {
json match {
@@ -44,6 +44,7 @@ object ScalaTargetType {
case _ => JsError(Seq())
}
}
+
}
case object Scala2 extends ScalaTargetType {
@@ -65,4 +66,5 @@ object ScalaTargetType {
case object Typelevel extends ScalaTargetType {
def defaultScalaTarget: ScalaTarget = ScalaTarget.Typelevel.default
}
+
}
diff --git a/api/src/main/scala/com.olegych.scastie.api/ScalaVersions.scala b/api/src/main/scala/com.olegych.scastie.api/ScalaVersions.scala
index 86f09288d..21b334a82 100644
--- a/api/src/main/scala/com.olegych.scastie.api/ScalaVersions.scala
+++ b/api/src/main/scala/com.olegych.scastie.api/ScalaVersions.scala
@@ -3,19 +3,20 @@ package com.olegych.scastie.api
import com.olegych.scastie.buildinfo.BuildInfo
object ScalaVersions {
+
def suggestedScalaVersions(tpe: ScalaTargetType): List[String] = {
val versions = tpe match {
case ScalaTargetType.Scala3 => List(BuildInfo.stableLTS, BuildInfo.stableNext)
- case ScalaTargetType.JS => List(BuildInfo.stableLTS, BuildInfo.stableNext, BuildInfo.latest213, BuildInfo.latest212)
- case _ => List(BuildInfo.latest213, BuildInfo.latest212)
+ case ScalaTargetType.JS =>
+ List(BuildInfo.stableLTS, BuildInfo.stableNext, BuildInfo.latest213, BuildInfo.latest212)
+ case _ => List(BuildInfo.latest213, BuildInfo.latest212)
}
versions.distinct
}
def allVersions(tpe: ScalaTargetType): List[String] = {
val versions = tpe match {
- case ScalaTargetType.Scala3 =>
- List(
+ case ScalaTargetType.Scala3 => List(
BuildInfo.latestNext,
BuildInfo.stableNext,
BuildInfo.latestLTS,
@@ -39,10 +40,10 @@ object ScalaVersions {
"3.0.1",
"3.0.0"
)
- case ScalaTargetType.JS =>
- allVersions(ScalaTargetType.Scala3) ++ allVersions(ScalaTargetType.Scala2).filter(v => v.startsWith("2.12") || v.startsWith("2.13"))
- case _ =>
- List(
+ case ScalaTargetType.JS => allVersions(ScalaTargetType.Scala3) ++ allVersions(ScalaTargetType.Scala2).filter(v =>
+ v.startsWith("2.12") || v.startsWith("2.13")
+ )
+ case _ => List(
BuildInfo.latest213,
"2.13.13",
"2.13.12",
@@ -100,6 +101,5 @@ object ScalaVersions {
versions.distinct
}
- def find(tpe: ScalaTargetType, sv: String): String =
- allVersions(tpe).find(_.startsWith(sv)).getOrElse(sv)
+ def find(tpe: ScalaTargetType, sv: String): String = allVersions(tpe).find(_.startsWith(sv)).getOrElse(sv)
}
diff --git a/api/src/main/scala/com.olegych.scastie.api/SnippetId.scala b/api/src/main/scala/com.olegych.scastie.api/SnippetId.scala
index f1e2685be..615f2dd51 100644
--- a/api/src/main/scala/com.olegych.scastie.api/SnippetId.scala
+++ b/api/src/main/scala/com.olegych.scastie.api/SnippetId.scala
@@ -14,23 +14,21 @@ case class User(login: String, name: Option[String], avatar_url: String) {
}
object SnippetUserPart {
- implicit val formatSnippetUserPart: OFormat[SnippetUserPart] =
- Json.format[SnippetUserPart]
+ implicit val formatSnippetUserPart: OFormat[SnippetUserPart] = Json.format[SnippetUserPart]
}
case class SnippetUserPart(login: String, update: Int = 0)
object SnippetId {
- implicit val formatSnippetId: OFormat[SnippetId] =
- Json.format[SnippetId]
+ implicit val formatSnippetId: OFormat[SnippetId] = Json.format[SnippetId]
}
case class SnippetId(base64UUID: String, user: Option[SnippetUserPart]) {
+
def isOwnedBy(user2: Option[User]): Boolean = {
(user, user2) match {
- case (Some(SnippetUserPart(snippetLogin, _)), Some(User(userLogin, _, _))) =>
- snippetLogin == userLogin
- case _ => false
+ case (Some(SnippetUserPart(snippetLogin, _)), Some(User(userLogin, _, _))) => snippetLogin == userLogin
+ case _ => false
}
}
@@ -38,9 +36,8 @@ case class SnippetId(base64UUID: String, user: Option[SnippetUserPart]) {
def url: String = {
this match {
- case SnippetId(uuid, None) => uuid
- case SnippetId(uuid, Some(SnippetUserPart(login, update))) =>
- s"$login/$uuid/$update"
+ case SnippetId(uuid, None) => uuid
+ case SnippetId(uuid, Some(SnippetUserPart(login, update))) => s"$login/$uuid/$update"
}
}
@@ -48,4 +45,5 @@ case class SnippetId(base64UUID: String, user: Option[SnippetUserPart]) {
val middle = url
s"/api/${Shared.scalaJsHttpPathPrefix}/$middle/$end"
}
+
}
diff --git a/api/src/main/scala/com.olegych.scastie.api/SnippetProgress.scala b/api/src/main/scala/com.olegych.scastie.api/SnippetProgress.scala
index 9db7b39b3..314b2d3ef 100644
--- a/api/src/main/scala/com.olegych.scastie.api/SnippetProgress.scala
+++ b/api/src/main/scala/com.olegych.scastie.api/SnippetProgress.scala
@@ -3,44 +3,45 @@ package com.olegych.scastie.api
import play.api.libs.json._
object SnippetProgress {
- def default: SnippetProgress =
- SnippetProgress(
- ts = None,
- id = None,
- snippetId = None,
- userOutput = None,
- sbtOutput = None,
- compilationInfos = Nil,
- instrumentations = Nil,
- runtimeError = None,
- scalaJsContent = None,
- scalaJsSourceMapContent = None,
- isDone = true,
- isTimeout = false,
- isSbtError = false,
- isForcedProgramMode = false
- )
+
+ def default: SnippetProgress = SnippetProgress(
+ ts = None,
+ id = None,
+ snippetId = None,
+ userOutput = None,
+ sbtOutput = None,
+ compilationInfos = Nil,
+ instrumentations = Nil,
+ runtimeError = None,
+ scalaJsContent = None,
+ scalaJsSourceMapContent = None,
+ isDone = true,
+ isTimeout = false,
+ isSbtError = false,
+ isForcedProgramMode = false
+ )
implicit val formatSnippetProgress: OFormat[SnippetProgress] = Json.format[SnippetProgress]
}
case class SnippetProgress(
- ts: Option[Long],
- id: Option[Long],
- snippetId: Option[SnippetId],
- userOutput: Option[ProcessOutput],
- sbtOutput: Option[ProcessOutput],
- compilationInfos: List[Problem],
- instrumentations: List[Instrumentation],
- runtimeError: Option[RuntimeError],
- scalaJsContent: Option[String],
- scalaJsSourceMapContent: Option[String],
- isDone: Boolean,
- isTimeout: Boolean,
- isSbtError: Boolean,
- isForcedProgramMode: Boolean
+ ts: Option[Long],
+ id: Option[Long],
+ snippetId: Option[SnippetId],
+ userOutput: Option[ProcessOutput],
+ sbtOutput: Option[ProcessOutput],
+ compilationInfos: List[Problem],
+ instrumentations: List[Instrumentation],
+ runtimeError: Option[RuntimeError],
+ scalaJsContent: Option[String],
+ scalaJsSourceMapContent: Option[String],
+ isDone: Boolean,
+ isTimeout: Boolean,
+ isSbtError: Boolean,
+ isForcedProgramMode: Boolean
) {
- def isFailure: Boolean = isTimeout || isSbtError || runtimeError.nonEmpty || compilationInfos.exists(_.severity == Error)
+ def isFailure: Boolean =
+ isTimeout || isSbtError || runtimeError.nonEmpty || compilationInfos.exists(_.severity == Error)
override def toString: String = Json.toJsObject(this).toString()
}
diff --git a/api/src/main/scala/com.olegych.scastie.api/StatusProgress.scala b/api/src/main/scala/com.olegych.scastie.api/StatusProgress.scala
index 55bcaa229..f97007c2c 100644
--- a/api/src/main/scala/com.olegych.scastie.api/StatusProgress.scala
+++ b/api/src/main/scala/com.olegych.scastie.api/StatusProgress.scala
@@ -3,25 +3,26 @@ package com.olegych.scastie.api
import play.api.libs.json._
object SbtRunnerState {
- implicit val formatSbtRunnerState: OFormat[SbtRunnerState] =
- Json.format[SbtRunnerState]
+ implicit val formatSbtRunnerState: OFormat[SbtRunnerState] = Json.format[SbtRunnerState]
}
case class SbtRunnerState(
- config: Inputs,
- tasks: Vector[TaskId],
- sbtState: SbtState
+ config: Inputs,
+ tasks: Vector[TaskId],
+ sbtState: SbtState
)
+
sealed trait StatusProgress
+
object StatusProgress {
+
implicit object StatusProgressFormat extends Format[StatusProgress] {
private val formatSbt = Json.format[StatusProgress.Sbt]
def writes(status: StatusProgress): JsValue = {
status match {
- case StatusProgress.KeepAlive =>
- JsObject(Seq("tpe" -> JsString("StatusProgress.KeepAlive")))
+ case StatusProgress.KeepAlive => JsObject(Seq("tpe" -> JsString("StatusProgress.KeepAlive")))
case runners: StatusProgress.Sbt =>
formatSbt.writes(runners) ++ JsObject(Seq("tpe" -> JsString("StatusProgress.Sbt")))
}
@@ -29,15 +30,11 @@ object StatusProgress {
def reads(json: JsValue): JsResult[StatusProgress] = {
json match {
- case obj: JsObject =>
- obj.value.get("tpe").orElse(obj.value.get("$type")) match {
- case Some(tpe) =>
- tpe match {
- case JsString("StatusProgress.KeepAlive") =>
- JsSuccess(StatusProgress.KeepAlive)
+ case obj: JsObject => obj.value.get("tpe").orElse(obj.value.get("$type")) match {
+ case Some(tpe) => tpe match {
+ case JsString("StatusProgress.KeepAlive") => JsSuccess(StatusProgress.KeepAlive)
- case JsString("StatusProgress.Sbt") =>
- formatSbt.reads(json)
+ case JsString("StatusProgress.Sbt") => formatSbt.reads(json)
case _ => JsError(Seq())
}
@@ -46,8 +43,9 @@ object StatusProgress {
case _ => JsError(Seq())
}
}
+
}
- case object KeepAlive extends StatusProgress
+ case object KeepAlive extends StatusProgress
case class Sbt(runners: Vector[SbtRunnerState]) extends StatusProgress
}
diff --git a/api/src/main/scala/com.olegych.scastie.api/TaskId.scala b/api/src/main/scala/com.olegych.scastie.api/TaskId.scala
index e811b87cf..3a1c5122c 100644
--- a/api/src/main/scala/com.olegych.scastie.api/TaskId.scala
+++ b/api/src/main/scala/com.olegych.scastie.api/TaskId.scala
@@ -1,12 +1,10 @@
package com.olegych.scastie.api
import play.api.libs.json._
-
import play.api.libs.json.OFormat
object TaskId {
- implicit val formatSbtRunTaskId: OFormat[TaskId] =
- Json.format[TaskId]
+ implicit val formatSbtRunTaskId: OFormat[TaskId] = Json.format[TaskId]
}
case class TaskId(snippetId: SnippetId)
diff --git a/balancer/src/main/scala/com.olegych.scastie.balancer/DispatchActor.scala b/balancer/src/main/scala/com.olegych.scastie.balancer/DispatchActor.scala
index 3f0181550..80520cd63 100644
--- a/balancer/src/main/scala/com.olegych.scastie.balancer/DispatchActor.scala
+++ b/balancer/src/main/scala/com.olegych.scastie.balancer/DispatchActor.scala
@@ -1,5 +1,11 @@
package com.olegych.scastie.balancer
+import java.nio.file.Paths
+import java.time.Instant
+import java.util.concurrent.Executors
+import scala.concurrent._
+import scala.concurrent.duration._
+
import akka.actor.Actor
import akka.actor.ActorLogging
import akka.actor.ActorRef
@@ -19,12 +25,6 @@ import com.olegych.scastie.storage.mongodb._
import com.olegych.scastie.util._
import com.typesafe.config.ConfigFactory
-import java.nio.file.Paths
-import java.time.Instant
-import java.util.concurrent.Executors
-import scala.concurrent._
-import scala.concurrent.duration._
-
case class Address(host: String, port: Int)
case class SbtConfig(config: String)
@@ -63,27 +63,25 @@ case object Ping
class DispatchActor(progressActor: ActorRef, statusActor: ActorRef)
// extends PersistentActor with AtLeastOnceDelivery
- extends Actor
- with ActorLogging {
+ extends Actor
+ with ActorLogging {
- override def supervisorStrategy: SupervisorStrategy = OneForOneStrategy() {
- case e =>
- log.error(e, "failure")
- SupervisorStrategy.resume
+ override def supervisorStrategy: SupervisorStrategy = OneForOneStrategy() { case e =>
+ log.error(e, "failure")
+ SupervisorStrategy.resume
}
- private val config =
- ConfigFactory.load().getConfig("com.olegych.scastie.balancer")
- private val host = config.getString("remote-hostname")
+ private val config = ConfigFactory.load().getConfig("com.olegych.scastie.balancer")
+ private val host = config.getString("remote-hostname")
private val sbtPortsStart = config.getInt("remote-sbt-ports-start")
- private val sbtPortsSize = config.getInt("remote-sbt-ports-size")
+ private val sbtPortsSize = config.getInt("remote-sbt-ports-size")
private val sbtPorts = (0 until sbtPortsSize).map(sbtPortsStart + _)
private def connectRunner(
- runnerName: String,
- actorName: String,
- host: String
+ runnerName: String,
+ actorName: String,
+ host: String
)(port: Int): ((String, Int), ActorSelection) = {
val path = s"akka://$runnerName@$host:$port/user/$actorName"
log.info(s"Connecting to ${path}")
@@ -92,14 +90,12 @@ class DispatchActor(progressActor: ActorRef, statusActor: ActorRef)
(host, port) -> selection
}
- private var remoteSbtSelections =
- sbtPorts.map(connectRunner("SbtRunner", "SbtActor", host)).toMap
+ private var remoteSbtSelections = sbtPorts.map(connectRunner("SbtRunner", "SbtActor", host)).toMap
private var sbtLoadBalancer: SbtBalancer = {
- val sbtServers = remoteSbtSelections.to(Vector).map {
- case (_, ref) =>
- val state: SbtState = SbtState.Unknown
- Server(ref, Inputs.default, state)
+ val sbtServers = remoteSbtSelections.to(Vector).map { case (_, ref) =>
+ val state: SbtState = SbtState.Unknown
+ Server(ref, Inputs.default, state)
}
LoadBalancer(servers = sbtServers)
@@ -124,19 +120,19 @@ class DispatchActor(progressActor: ActorRef, statusActor: ActorRef)
val containerType = config.getString("snippets-storage")
- private val container =
- containerType match {
- case "memory" => new InMemoryContainer()
- case "mongo" => new MongoDBContainer()(ExecutionContext.fromExecutor(Executors.newWorkStealingPool()))
- case "mongo-local" => new MongoDBContainer(defaultConfig = false)(ExecutionContext.fromExecutor(Executors.newWorkStealingPool()))
- case "files" => new FilesystemContainer(
+ private val container = containerType match {
+ case "memory" => new InMemoryContainer()
+ case "mongo" => new MongoDBContainer()(ExecutionContext.fromExecutor(Executors.newWorkStealingPool()))
+ case "mongo-local" =>
+ new MongoDBContainer(defaultConfig = false)(ExecutionContext.fromExecutor(Executors.newWorkStealingPool()))
+ case "files" => new FilesystemContainer(
Paths.get(config.getString("snippets-dir")),
Paths.get(config.getString("old-snippets-dir"))
)(ExecutionContext.fromExecutorService(Executors.newCachedThreadPool()))
- case _ =>
- println("fallback to in-memory container")
- new InMemoryContainer
- }
+ case _ =>
+ println("fallback to in-memory container")
+ new InMemoryContainer
+ }
private def updateSbtBalancer(newSbtBalancer: SbtBalancer): Unit = {
if (sbtLoadBalancer != newSbtBalancer) {
@@ -146,11 +142,12 @@ class DispatchActor(progressActor: ActorRef, statusActor: ActorRef)
()
}
- //can be called from future
+ // can be called from future
private def run(inputsWithIpAndUser: InputsWithIpAndUser, snippetId: SnippetId): Unit = {
self ! Run(inputsWithIpAndUser, snippetId)
}
- //cannot be called from future
+
+ // cannot be called from future
private def run0(inputsWithIpAndUser: InputsWithIpAndUser, snippetId: SnippetId): Unit = {
val InputsWithIpAndUser(inputs, UserTrace(ip, user)) = inputsWithIpAndUser
@@ -172,8 +169,8 @@ class DispatchActor(progressActor: ActorRef, statusActor: ActorRef)
}
private def logError[T](f: Future[T]) = {
- f.recover {
- case e => log.error(e, "failed future")
+ f.recover { case e =>
+ log.error(e, "failed future")
}
}
@@ -188,7 +185,7 @@ class DispatchActor(progressActor: ActorRef, statusActor: ActorRef)
case x @ RunSnippet(inputsWithIpAndUser) =>
log.info(s"starting ${x}")
val InputsWithIpAndUser(inputs, UserTrace(_, user)) = inputsWithIpAndUser
- val sender = this.sender()
+ val sender = this.sender()
logError(container.create(inputs, user.map(u => UserLogin(u.login))).map { snippetId =>
sender ! snippetId
run(inputsWithIpAndUser, snippetId)
@@ -197,7 +194,7 @@ class DispatchActor(progressActor: ActorRef, statusActor: ActorRef)
case SaveSnippet(inputsWithIpAndUser) =>
val InputsWithIpAndUser(inputs, UserTrace(_, user)) = inputsWithIpAndUser
- val sender = this.sender()
+ val sender = this.sender()
logError(container.save(inputs, user.map(u => UserLogin(u.login))).map { snippetId =>
sender ! snippetId
run(inputsWithIpAndUser, snippetId)
@@ -207,15 +204,12 @@ class DispatchActor(progressActor: ActorRef, statusActor: ActorRef)
val sender = this.sender()
logError(container.update(snippetId, inputsWithIpAndUser.inputs).map { updatedSnippetId =>
sender ! updatedSnippetId
- updatedSnippetId.foreach(
- snippetIdU => run(inputsWithIpAndUser, snippetIdU)
- )
+ updatedSnippetId.foreach(snippetIdU => run(inputsWithIpAndUser, snippetIdU))
})
case ForkSnippet(snippetId, inputsWithIpAndUser) =>
- val InputsWithIpAndUser(inputs, UserTrace(_, user)) =
- inputsWithIpAndUser
- val sender = this.sender()
+ val InputsWithIpAndUser(inputs, UserTrace(_, user)) = inputsWithIpAndUser
+ val sender = this.sender()
logError(
container
.fork(snippetId, inputs, user.map(u => UserLogin(u.login)))
@@ -277,38 +271,33 @@ class DispatchActor(progressActor: ActorRef, statusActor: ActorRef)
logError(
container
.appendOutput(progress)
- .recover {
- case e =>
- log.error(e, s"failed to save $progress from $sender")
- e
+ .recover { case e =>
+ log.error(e, s"failed to save $progress from $sender")
+ e
}
.map(sender ! _)
)
- case done: Done =>
- done.progress.snippetId.foreach { sid =>
+ case done: Done => done.progress.snippetId.foreach { sid =>
val newBalancer = sbtLoadBalancer.done(TaskId(sid))
newBalancer match {
- case Some(newBalancer) =>
- updateSbtBalancer(newBalancer)
+ case Some(newBalancer) => updateSbtBalancer(newBalancer)
case None =>
if (done.retries >= 0) {
system.scheduler.scheduleOnce(1.second) {
self ! done.copy(retries = done.retries - 1)
}
} else {
- val taskIds =
- sbtLoadBalancer.servers.flatMap(_.mailbox.map(_.taskId))
+ val taskIds = sbtLoadBalancer.servers.flatMap(_.mailbox.map(_.taskId))
log.error(s"stopped retrying to update ${taskIds} with ${done}")
}
}
}
- case event: DisassociatedEvent =>
- for {
+ case event: DisassociatedEvent => for {
host <- event.remoteAddress.host
port <- event.remoteAddress.port
- ref <- remoteSbtSelections.get((host, port))
+ ref <- remoteSbtSelections.get((host, port))
} {
log.warning("removing disconnected: {}", ref)
val previousRemoteSbtSelections = remoteSbtSelections
@@ -318,11 +307,9 @@ class DispatchActor(progressActor: ActorRef, statusActor: ActorRef)
}
}
- case SbtUp =>
- log.info("SbtUp")
+ case SbtUp => log.info("SbtUp")
- case Replay(SbtRun(snippetId, inputs, progressActor, snippetActor)) =>
- log.info("Replay: " + inputs.code)
+ case Replay(SbtRun(snippetId, inputs, progressActor, snippetActor)) => log.info("Replay: " + inputs.code)
case SbtRunnerConnect(runnerHostname, runnerAkkaPort) =>
if (!remoteSbtSelections.contains((runnerHostname, runnerAkkaPort))) {
@@ -344,14 +331,11 @@ class DispatchActor(progressActor: ActorRef, statusActor: ActorRef)
)
}
- case ReceiveStatus(requester) =>
- sender() ! LoadBalancerInfo(sbtLoadBalancer, requester)
+ case ReceiveStatus(requester) => sender() ! LoadBalancerInfo(sbtLoadBalancer, requester)
- case statusProgress: StatusProgress =>
- statusActor ! statusProgress
+ case statusProgress: StatusProgress => statusActor ! statusProgress
- case run: Run =>
- run0(run.inputsWithIpAndUser, run.snippetId)
+ case run: Run => run0(run.inputsWithIpAndUser, run.snippetId)
case ping: Ping.type =>
implicit val timeout: Timeout = Timeout(10.seconds)
logError(Future.sequence {
@@ -360,10 +344,11 @@ class DispatchActor(progressActor: ActorRef, statusActor: ActorRef)
.map { _ =>
log.info(s"pinged ${s.ref} server")
}
- .recover {
- case e => log.error(e, s"couldn't ping ${s} server")
+ .recover { case e =>
+ log.error(e, s"couldn't ping ${s} server")
}
}
})
}
+
}
diff --git a/balancer/src/main/scala/com.olegych.scastie.balancer/LoadBalancer.scala b/balancer/src/main/scala/com.olegych.scastie.balancer/LoadBalancer.scala
index 950f0f473..66efe5ce0 100644
--- a/balancer/src/main/scala/com.olegych.scastie.balancer/LoadBalancer.scala
+++ b/balancer/src/main/scala/com.olegych.scastie.balancer/LoadBalancer.scala
@@ -1,23 +1,25 @@
package com.olegych.scastie.balancer
-import java.time.Instant
import java.time.temporal.ChronoUnit
+import java.time.Instant
+import scala.util.Random
import com.olegych.scastie.api._
import org.slf4j.LoggerFactory
-import scala.util.Random
-
case class Ip(v: String)
case class Task(config: Inputs, ip: Ip, taskId: TaskId, ts: Instant)
case class TaskHistory(data: Vector[Task], maxSize: Int) {
+
def add(task: Task): TaskHistory = {
val cappedData = if (data.length < maxSize) data else data.drop(1)
copy(data = cappedData :+ task)
}
+
}
+
case class LoadBalancer[R, S <: ServerState](servers: Vector[Server[R, S]]) {
private val log = LoggerFactory.getLogger(getClass)
@@ -41,21 +43,26 @@ case class LoadBalancer[R, S <: ServerState](servers: Vector[Server[R, S]]) {
def add(task: Task): Option[(Server[R, S], LoadBalancer[R, S])] = {
log.info("Task added: {}", task.taskId)
- val (availableServers, unavailableServers) =
- servers.partition(_.state.isReady)
+ val (availableServers, unavailableServers) = servers.partition(_.state.isReady)
def lastTenMinutes(v: Vector[Task]) = v.filter(_.ts.isAfter(Instant.now.minus(10, ChronoUnit.MINUTES)))
- def lastWithIp(v: Vector[Task]) = lastTenMinutes(v.filter(_.ip == task.ip)).lastOption
+ def lastWithIp(v: Vector[Task]) = lastTenMinutes(v.filter(_.ip == task.ip)).lastOption
if (availableServers.nonEmpty) {
val selectedServer = availableServers.maxBy { s =>
(
- s.mailbox.length < 3, //allow reload if server gets busy
- !s.currentConfig.needsReload(task.config), //pick those without need for reload
- -s.mailbox.length, //then those least busy
- lastTenMinutes(s.mailbox ++ s.history.data).exists(!_.config.needsReload(task.config)), //then those which use(d) this config
- lastWithIp(s.mailbox).orElse(lastWithIp(s.history.data)).map(_.ts.toEpochMilli), //then one most recently used by this ip, if any
- s.mailbox.lastOption.orElse(s.history.data.lastOption).map(-_.ts.toEpochMilli).getOrElse(0L) //then one least recently used
+ s.mailbox.length < 3, // allow reload if server gets busy
+ !s.currentConfig.needsReload(task.config), // pick those without need for reload
+ -s.mailbox.length, // then those least busy
+ lastTenMinutes(s.mailbox ++ s.history.data)
+ .exists(!_.config.needsReload(task.config)), // then those which use(d) this config
+ lastWithIp(s.mailbox)
+ .orElse(lastWithIp(s.history.data))
+ .map(_.ts.toEpochMilli), // then one most recently used by this ip, if any
+ s.mailbox.lastOption
+ .orElse(s.history.data.lastOption)
+ .map(-_.ts.toEpochMilli)
+ .getOrElse(0L) // then one least recently used
)
}
val updatedServers = availableServers.map(old => if (old.id == selectedServer.id) old.add(task) else old)
@@ -63,7 +70,7 @@ case class LoadBalancer[R, S <: ServerState](servers: Vector[Server[R, S]]) {
(
selectedServer,
copy(
- servers = updatedServers ++ unavailableServers,
+ servers = updatedServers ++ unavailableServers
// history = updatedHistory
)
)
diff --git a/balancer/src/main/scala/com.olegych.scastie.balancer/ProgressActor.scala b/balancer/src/main/scala/com.olegych.scastie.balancer/ProgressActor.scala
index bde039147..162c59f32 100644
--- a/balancer/src/main/scala/com.olegych.scastie.balancer/ProgressActor.scala
+++ b/balancer/src/main/scala/com.olegych.scastie.balancer/ProgressActor.scala
@@ -1,15 +1,15 @@
package com.olegych.scastie
package balancer
-import akka.NotUsed
+import scala.collection.mutable.{Map => MMap, Queue => MQueue}
+import scala.concurrent.duration.DurationLong
+
import akka.actor.{Actor, ActorRef}
import akka.stream.scaladsl.Source
+import akka.NotUsed
import com.olegych.scastie.api._
import com.olegych.scastie.util.GraphStageForwarder
-import scala.collection.mutable.{Map => MMap, Queue => MQueue}
-import scala.concurrent.duration.DurationLong
-
case class SubscribeProgress(snippetId: SnippetId)
private case class Cleanup(snippetId: SnippetId)
@@ -25,15 +25,16 @@ class ProgressActor extends Actor {
val (source, _) = getOrCreateNewSubscriberInfo(snippetId, self)
sender() ! source
- case snippetProgress: SnippetProgress =>
- snippetProgress.snippetId.foreach { snippetId =>
+ case snippetProgress: SnippetProgress => snippetProgress.snippetId.foreach { snippetId =>
getOrCreateNewSubscriberInfo(snippetId, self)
queuedMessages.getOrElseUpdate(snippetId, MQueue()).enqueue(snippetProgress)
sendQueuedMessages(snippetId, self)
}
case (snippedId: SnippetId, graphStageForwarderActor: ActorRef) =>
- subscribers.get(snippedId).foreach(s => subscribers.update(snippedId, s.copy(_2 = Some(graphStageForwarderActor))))
+ subscribers
+ .get(snippedId)
+ .foreach(s => subscribers.update(snippedId, s.copy(_2 = Some(graphStageForwarderActor))))
sendQueuedMessages(snippedId, self)
case Cleanup(snippetId) =>
@@ -48,13 +49,13 @@ class ProgressActor extends Actor {
)
}
- private def sendQueuedMessages(snippetId: SnippetId, self: ActorRef): Unit =
- for {
- messageQueue <- queuedMessages.get(snippetId).toSeq
- (_, Some(graphStageForwarderActor)) <- subscribers.get(snippetId).toSeq
- message <- messageQueue.dequeueAll(_ => true)
- } yield {
- graphStageForwarderActor ! message
- if (message.isDone) context.system.scheduler.scheduleOnce(3.seconds, self, Cleanup(snippetId))(context.dispatcher)
- }
+ private def sendQueuedMessages(snippetId: SnippetId, self: ActorRef): Unit = for {
+ messageQueue <- queuedMessages.get(snippetId).toSeq
+ (_, Some(graphStageForwarderActor)) <- subscribers.get(snippetId).toSeq
+ message <- messageQueue.dequeueAll(_ => true)
+ } yield {
+ graphStageForwarderActor ! message
+ if (message.isDone) context.system.scheduler.scheduleOnce(3.seconds, self, Cleanup(snippetId))(context.dispatcher)
+ }
+
}
diff --git a/balancer/src/main/scala/com.olegych.scastie.balancer/Server.scala b/balancer/src/main/scala/com.olegych.scastie.balancer/Server.scala
index 217995bd6..a1513a87e 100644
--- a/balancer/src/main/scala/com.olegych.scastie.balancer/Server.scala
+++ b/balancer/src/main/scala/com.olegych.scastie.balancer/Server.scala
@@ -1,31 +1,32 @@
package com.olegych.scastie.balancer
-import com.olegych.scastie.api._
-
import scala.util.Random
+import com.olegych.scastie.api._
+
case class Server[R, S](
- ref: R,
- lastConfig: Inputs,
- state: S,
- mailbox: Vector[Task] = Vector.empty,
- history: TaskHistory = TaskHistory(Vector.empty, 1000),
- id: Int = Random.nextInt(),
+ ref: R,
+ lastConfig: Inputs,
+ state: S,
+ mailbox: Vector[Task] = Vector.empty,
+ history: TaskHistory = TaskHistory(Vector.empty, 1000),
+ id: Int = Random.nextInt()
) {
def currentTaskId: Option[TaskId] = mailbox.headOption.map(_.taskId)
- def currentConfig: Inputs = mailbox.headOption.map(_.config).getOrElse(lastConfig)
+ def currentConfig: Inputs = mailbox.headOption.map(_.config).getOrElse(lastConfig)
def done(taskId: TaskId): Server[R, S] = {
val (newMailbox, done) = mailbox.partition(_.taskId != taskId)
copy(
lastConfig = done.headOption.map(_.config).getOrElse(lastConfig),
mailbox = newMailbox,
- history = done.foldLeft(history)(_.add(_)),
+ history = done.foldLeft(history)(_.add(_))
)
}
def add(task: Task): Server[R, S] = {
copy(mailbox = mailbox :+ task)
}
+
}
diff --git a/balancer/src/main/scala/com.olegych.scastie.balancer/StatusActor.scala b/balancer/src/main/scala/com.olegych.scastie.balancer/StatusActor.scala
index 23b39dbdf..3761fc1af 100644
--- a/balancer/src/main/scala/com.olegych.scastie.balancer/StatusActor.scala
+++ b/balancer/src/main/scala/com.olegych.scastie.balancer/StatusActor.scala
@@ -1,14 +1,12 @@
package com.olegych.scastie.balancer
-import com.olegych.scastie.api._
-
-import akka.actor.{Actor, ActorLogging, ActorRef, Props}
-import akka.stream.scaladsl.Source
import java.util.concurrent.TimeUnit
-
import scala.collection.mutable
import scala.concurrent.duration._
+import akka.actor.{Actor, ActorLogging, ActorRef, Props}
+import akka.stream.scaladsl.Source
+import com.olegych.scastie.api._
import com.olegych.scastie.util.GraphStageForwarder
case object SubscribeStatus
@@ -21,6 +19,7 @@ case class SetDispatcher(dispatchActor: ActorRef)
object StatusActor {
def props: Props = Props(new StatusActor)
}
+
class StatusActor private () extends Actor with ActorLogging {
private var publishers = mutable.Buffer.empty[ActorRef]
@@ -29,16 +28,14 @@ class StatusActor private () extends Actor with ActorLogging {
override def receive: Receive = {
case SubscribeStatus => {
- val publisherGraphStage =
- new GraphStageForwarder("StatusActor-GraphStageForwarder", self, None)
+ val publisherGraphStage = new GraphStageForwarder("StatusActor-GraphStageForwarder", self, None)
- val source =
- Source
- .fromGraph(publisherGraphStage)
- .keepAlive(
- FiniteDuration(1, TimeUnit.SECONDS),
- () => StatusProgress.KeepAlive
- )
+ val source = Source
+ .fromGraph(publisherGraphStage)
+ .keepAlive(
+ FiniteDuration(1, TimeUnit.SECONDS),
+ () => StatusProgress.KeepAlive
+ )
sender() ! source
}
@@ -63,14 +60,14 @@ class StatusActor private () extends Actor with ActorLogging {
private def convertSbt(newSbtBalancer: SbtBalancer): StatusProgress = {
StatusProgress.Sbt(
- newSbtBalancer.servers.map(
- server =>
- SbtRunnerState(
- config = server.lastConfig,
- tasks = server.mailbox.map(_.taskId),
- sbtState = server.state
+ newSbtBalancer.servers.map(server =>
+ SbtRunnerState(
+ config = server.lastConfig,
+ tasks = server.mailbox.map(_.taskId),
+ sbtState = server.state
)
)
)
}
+
}
diff --git a/balancer/src/test/scala/com.olegych.scastie.balancer/LoadBalancerRecoveryTest.scala b/balancer/src/test/scala/com.olegych.scastie.balancer/LoadBalancerRecoveryTest.scala
index 13208d021..79940b588 100644
--- a/balancer/src/test/scala/com.olegych.scastie.balancer/LoadBalancerRecoveryTest.scala
+++ b/balancer/src/test/scala/com.olegych.scastie.balancer/LoadBalancerRecoveryTest.scala
@@ -1,5 +1,8 @@
package com.olegych.scastie.balancer
+import scala.concurrent._
+import scala.concurrent.duration._
+
import akka.actor.{ActorSystem, Props}
import akka.pattern.ask
import akka.testkit.{ImplicitSender, TestKit, TestProbe}
@@ -8,31 +11,27 @@ import com.olegych.scastie.api._
import com.olegych.scastie.sbt._
import com.olegych.scastie.util.ReconnectInfo
import com.typesafe.config.{Config, ConfigFactory}
-import org.scalatest.BeforeAndAfterAll
import org.scalatest.funsuite.AnyFunSuiteLike
-
-import scala.concurrent._
-import scala.concurrent.duration._
+import org.scalatest.BeforeAndAfterAll
class LoadBalancerRecoveryTest()
- extends TestKit(
- ActorSystem("LoadBalancerRecoveryTest", RemotePortConfig(0))
- )
- with ImplicitSender
- with AnyFunSuiteLike
- with BeforeAndAfterAll {
+ extends TestKit(
+ ActorSystem("LoadBalancerRecoveryTest", RemotePortConfig(0))
+ )
+ with ImplicitSender
+ with AnyFunSuiteLike
+ with BeforeAndAfterAll {
// import system.dispatcher
implicit val timeout: Timeout = Timeout(25.seconds)
test("recover from crash") {
- val crash =
- """|val f = classOf[sun.misc.Unsafe].getDeclaredField("theUnsafe")
- |f.setAccessible(true)
- |val unsafe = f.get(null).asInstanceOf[sun.misc.Unsafe]
- |println("TRYING TO CRASH JVM")
- |unsafe.putLong(0, 0)
- |println("SHOULD HAVE CRASHED!")""".stripMargin
+ val crash = """|val f = classOf[sun.misc.Unsafe].getDeclaredField("theUnsafe")
+ |f.setAccessible(true)
+ |val unsafe = f.get(null).asInstanceOf[sun.misc.Unsafe]
+ |println("TRYING TO CRASH JVM")
+ |unsafe.putLong(0, 0)
+ |println("SHOULD HAVE CRASHED!")""".stripMargin
val code1 = "println(1)"
val code3 = "println(2)"
@@ -54,39 +53,37 @@ class LoadBalancerRecoveryTest()
}
private val serverAkkaPort = 15000
- private val webSystem = ActorSystem("Web", RemotePortConfig(serverAkkaPort))
+ private val webSystem = ActorSystem("Web", RemotePortConfig(serverAkkaPort))
private val sbtAkkaPort = 5150
- private val sbtSystem =
- ActorSystem("SbtRunner", RemotePortConfig(sbtAkkaPort))
+ private val sbtSystem = ActorSystem("SbtRunner", RemotePortConfig(sbtAkkaPort))
- private val progressActor = TestProbe()
- private val statusActor = TestProbe()
+ private val progressActor = TestProbe()
+ private val statusActor = TestProbe()
private val sbtActorReadyProbe = TestProbe()
private val localhost = "127.0.0.1"
- private val sbtActor =
- sbtSystem.actorOf(
- Props(
- new SbtActor(
- system = sbtSystem,
- runTimeout = 10.seconds,
- sbtReloadTimeout = 20.seconds,
- isProduction = false,
- readyRef = Some(sbtActorReadyProbe.ref),
- reconnectInfo = Some(
- ReconnectInfo(
- serverHostname = localhost,
- serverAkkaPort = serverAkkaPort,
- actorHostname = localhost,
- actorAkkaPort = sbtAkkaPort
- )
+ private val sbtActor = sbtSystem.actorOf(
+ Props(
+ new SbtActor(
+ system = sbtSystem,
+ runTimeout = 10.seconds,
+ sbtReloadTimeout = 20.seconds,
+ isProduction = false,
+ readyRef = Some(sbtActorReadyProbe.ref),
+ reconnectInfo = Some(
+ ReconnectInfo(
+ serverHostname = localhost,
+ serverAkkaPort = serverAkkaPort,
+ actorHostname = localhost,
+ actorAkkaPort = sbtAkkaPort
)
)
- ),
- name = "SbtActor"
- )
+ )
+ ),
+ name = "SbtActor"
+ )
sbtActorReadyProbe.fishForMessage(60.seconds) {
case SbtActorReady => {
@@ -101,20 +98,19 @@ class LoadBalancerRecoveryTest()
}
}
- private val dispatchActor =
- webSystem.actorOf(
- Props(new DispatchActor(progressActor.ref, statusActor.ref)),
- name = "DispatchActor"
- )
+ private val dispatchActor = webSystem.actorOf(
+ Props(new DispatchActor(progressActor.ref, statusActor.ref)),
+ name = "DispatchActor"
+ )
private var id = 0
+
private def run(code: String): SnippetId = {
- val wrapped =
- s"""|object Main {
- | def main(args: Array[String]): Unit = {
- | $code
- | }
- |}""".stripMargin
+ val wrapped = s"""|object Main {
+ | def main(args: Array[String]): Unit = {
+ | $code
+ | }
+ |}""".stripMargin
val inputs = Inputs.default.copy(code = wrapped, _isWorksheetMode = false)
@@ -131,7 +127,7 @@ class LoadBalancerRecoveryTest()
}
private def waitFor(sid: SnippetId, ret: Map[SnippetId, String])(
- f: SnippetProgress => Boolean
+ f: SnippetProgress => Boolean
): Unit = {
progressActor.fishForMessage(50.seconds) {
@@ -159,22 +155,24 @@ class LoadBalancerRecoveryTest()
TestKit.shutdownActorSystem(sbtSystem)
TestKit.shutdownActorSystem(system)
}
+
}
object RemotePortConfig {
- def apply(port: Int): Config =
- ConfigFactory.parseString(
- s"""|akka {
- | actor {
- | provider = cluster
- | allow-java-serialization = on
- | }
- | remote {
- | artery.canonical {
- | hostname = "127.0.0.1"
- | port = $port
- | }
- | }
- |}""".stripMargin
- )
+
+ def apply(port: Int): Config = ConfigFactory.parseString(
+ s"""|akka {
+ | actor {
+ | provider = cluster
+ | allow-java-serialization = on
+ | }
+ | remote {
+ | artery.canonical {
+ | hostname = "127.0.0.1"
+ | port = $port
+ | }
+ | }
+ |}""".stripMargin
+ )
+
}
diff --git a/balancer/src/test/scala/com.olegych.scastie.balancer/LoadBalancerTest.scala b/balancer/src/test/scala/com.olegych.scastie.balancer/LoadBalancerTest.scala
index 3d0dc2c70..3679f2436 100644
--- a/balancer/src/test/scala/com.olegych.scastie.balancer/LoadBalancerTest.scala
+++ b/balancer/src/test/scala/com.olegych.scastie.balancer/LoadBalancerTest.scala
@@ -11,7 +11,7 @@ class LoadBalancerTest extends LoadBalancerTestUtils {
1 * "c2",
1 * "c3",
1 * "c4"
- ),
+ )
)
assertConfigs(add(balancer, sbtConfig("c8")))(
@@ -27,7 +27,7 @@ class LoadBalancerTest extends LoadBalancerTestUtils {
val balancer = LoadBalancer(
servers(
5 * "c1"
- ),
+ )
)
assertConfigs(add(balancer, sbtConfig("c1")))(
@@ -39,7 +39,7 @@ class LoadBalancerTest extends LoadBalancerTestUtils {
val balancer = LoadBalancer(
servers(
5 * "c1"
- ),
+ )
)
assertConfigs(add(balancer, sbtConfig("c2")))(
4 * "c1",
@@ -49,13 +49,13 @@ class LoadBalancerTest extends LoadBalancerTestUtils {
test("server notify when it's done") {
val balancer = LoadBalancer(
- servers(1 * "c1"),
+ servers(1 * "c1")
)
val server = balancer.servers.head
assert(server.mailbox.isEmpty)
- val c1 = sbtConfig("c1")
+ val c1 = sbtConfig("c1")
val taskId = TestTaskId(1)
val (assigned, balancer0) = balancer.add(Task(c1, nextIp, taskId, Instant.now)).get
@@ -69,16 +69,15 @@ class LoadBalancerTest extends LoadBalancerTestUtils {
test("run two tasks") {
val balancer = LoadBalancer(
- servers(1 * "c1"),
+ servers(1 * "c1")
)
val server = balancer.servers.head
assert(server.mailbox.isEmpty)
assert(server.currentTaskId.isEmpty)
- val taskId1 = TestTaskId(1)
- val (assigned0, balancer0) =
- balancer.add(Task(sbtConfig("c1"), nextIp, taskId1, Instant.now)).get
+ val taskId1 = TestTaskId(1)
+ val (assigned0, balancer0) = balancer.add(Task(sbtConfig("c1"), nextIp, taskId1, Instant.now)).get
val server0 = balancer0.servers.head
@@ -86,21 +85,20 @@ class LoadBalancerTest extends LoadBalancerTestUtils {
assert(server0.mailbox.size == 1)
assert(server0.currentTaskId.contains(taskId1))
- val taskId2 = TestTaskId(2)
- val (assigned1, balancer1) =
- balancer0.add(Task(sbtConfig("c2"), nextIp, taskId2, Instant.now)).get
+ val taskId2 = TestTaskId(2)
+ val (assigned1, balancer1) = balancer0.add(Task(sbtConfig("c2"), nextIp, taskId2, Instant.now)).get
val server1 = balancer1.servers.head
assert(server1.mailbox.size == 2)
assert(server1.currentTaskId.contains(taskId1))
val balancer2 = balancer1.done(taskId1).get
- val server2 = balancer2.servers.head
+ val server2 = balancer2.servers.head
assert(server2.mailbox.size == 1)
assert(server2.currentTaskId.contains(taskId2))
val balancer3 = balancer2.done(taskId2).get
- val server3 = balancer3.servers.head
+ val server3 = balancer3.servers.head
assert(server3.mailbox.isEmpty)
assert(server3.currentTaskId.isEmpty)
}
@@ -109,7 +107,7 @@ class LoadBalancerTest extends LoadBalancerTestUtils {
val ref = TestServerRef(1)
val balancer = LoadBalancer(
- Vector(Server(ref, sbtConfig("c1"), TestState("default-state"))),
+ Vector(Server(ref, sbtConfig("c1"), TestState("default-state")))
)
assert(balancer.removeServer(ref).servers.isEmpty)
}
@@ -117,7 +115,7 @@ class LoadBalancerTest extends LoadBalancerTestUtils {
test("empty balancer") {
val emptyBalancer = LoadBalancer(
- servers = Vector(),
+ servers = Vector()
)
val task = Task(code("c1"), nextIp, TestTaskId(1), Instant.now)
diff --git a/balancer/src/test/scala/com.olegych.scastie.balancer/LoadBalancerTestUtils.scala b/balancer/src/test/scala/com.olegych.scastie.balancer/LoadBalancerTestUtils.scala
index eb69872de..a4953d8a2 100644
--- a/balancer/src/test/scala/com.olegych.scastie.balancer/LoadBalancerTestUtils.scala
+++ b/balancer/src/test/scala/com.olegych.scastie.balancer/LoadBalancerTestUtils.scala
@@ -3,14 +3,15 @@ package com.olegych.scastie.balancer
import java.time.Instant
import com.olegych.scastie.api._
-import org.scalatest.Assertion
import org.scalatest.funsuite.AnyFunSuite
+import org.scalatest.Assertion
object TestTaskId {
def apply(i: Int) = TaskId(SnippetId(i.toString, None))
}
case class TestServerRef(id: Int)
+
case class TestState(state: String, ready: Boolean = true) extends ServerState {
def isReady: Boolean = ready
}
@@ -21,6 +22,7 @@ trait LoadBalancerTestUtils extends AnyFunSuite with TestUtils {
type TestLoadBalancer0 = LoadBalancer[TestServerRef, TestState]
@transient private var taskId = 1000
+
def add(balancer: TestLoadBalancer0, config: Inputs): TestLoadBalancer0 = synchronized {
val (_, balancer0) = balancer.add(Task(config, nextIp, TestTaskId(taskId), Instant.now)).get
taskId += 1
@@ -29,20 +31,22 @@ trait LoadBalancerTestUtils extends AnyFunSuite with TestUtils {
// Ordering only for debug purposes
object Multiset {
- def apply[T: Ordering](xs: Seq[T]): Multiset[T] =
- Multiset(xs.groupBy(x => x).map { case (k, vs) => (k, vs.size) })
+ def apply[T: Ordering](xs: Seq[T]): Multiset[T] = Multiset(xs.groupBy(x => x).map { case (k, vs) => (k, vs.size) })
}
+
case class Multiset[T: Ordering](inner: Map[T, Int]) {
+
override def toString: String = {
val size = inner.values.sum
inner.toList
.sortBy { case (k, v) => (-v, k) }
- .map {
- case (k, v) => s"$k($v)"
+ .map { case (k, v) =>
+ s"$k($v)"
}
.mkString("Multiset(", ", ", s") {$size}")
}
+
}
def assertConfigs(balancer: TestLoadBalancer0)(columns: Seq[String]*): Assertion = {
@@ -54,10 +58,11 @@ trait LoadBalancerTestUtils extends AnyFunSuite with TestUtils {
}
@transient private var serverId = 0
+
def server(
- c: String,
- mailbox: Vector[Task] = Vector(),
- state: TestState = TestState("default-state")
+ c: String,
+ mailbox: Vector[Task] = Vector(),
+ state: TestState = TestState("default-state")
): TestServer0 = synchronized {
val t = Server(TestServerRef(serverId), sbtConfig(c), state, mailbox)
serverId += 1
@@ -69,6 +74,7 @@ trait LoadBalancerTestUtils extends AnyFunSuite with TestUtils {
}
@transient private var currentIp = 0
+
def nextIp: Ip = synchronized {
val t = Ip("ip" + currentIp)
currentIp += 1
@@ -77,13 +83,17 @@ trait LoadBalancerTestUtils extends AnyFunSuite with TestUtils {
def server(v: Int): TestServerRef = TestServerRef(v)
- def code(code: String) = Inputs.default.copy(code = code)
+ def code(code: String) = Inputs.default.copy(code = code)
def sbtConfig(sbtConfig: String) = Inputs.default.copy(sbtConfigExtra = sbtConfig)
def history(columns: Seq[String]*): TaskHistory = {
- val records =
- columns.to(Vector).flatten.map(i => Task(Inputs.default.copy(code = i.toString), nextIp, TestTaskId(1), Instant.now)).reverse
+ val records = columns
+ .to(Vector)
+ .flatten
+ .map(i => Task(Inputs.default.copy(code = i.toString), nextIp, TestTaskId(1), Instant.now))
+ .reverse
TaskHistory(Vector(records: _*), maxSize = 20)
}
+
}
diff --git a/balancer/src/test/scala/com.olegych.scastie.balancer/TestUtils.scala b/balancer/src/test/scala/com.olegych.scastie.balancer/TestUtils.scala
index 7fc7bd3b9..68aba860a 100644
--- a/balancer/src/test/scala/com.olegych.scastie.balancer/TestUtils.scala
+++ b/balancer/src/test/scala/com.olegych.scastie.balancer/TestUtils.scala
@@ -2,7 +2,9 @@ package com.olegych.scastie
package balancer
trait TestUtils {
+
implicit class IntExtension(n: Int) {
def *[T](v: T): Seq[T] = List.fill(n)(v)
}
+
}
diff --git a/client/src/main/scala/com.olegych.scastie.client/AnsiColorFormatter.scala b/client/src/main/scala/com.olegych.scastie.client/AnsiColorFormatter.scala
index 3fbbe22a3..e0f11d6f7 100644
--- a/client/src/main/scala/com.olegych.scastie.client/AnsiColorFormatter.scala
+++ b/client/src/main/scala/com.olegych.scastie.client/AnsiColorFormatter.scala
@@ -5,41 +5,41 @@ import scala.io.AnsiColor
object AnsiColorFormatter extends AnsiColor {
private val colors = Map(
- BLACK -> "ansi-color-black",
- RED -> "ansi-color-red",
- GREEN -> "ansi-color-green",
- YELLOW -> "ansi-color-yellow",
- BLUE -> "ansi-color-blue",
- MAGENTA -> "ansi-color-magenta",
- CYAN -> "ansi-color-cyan",
- WHITE -> "ansi-color-white",
- BLACK_B -> "ansi-bg-color-black",
- RED_B -> "ansi-bg-color-red",
- GREEN_B -> "ansi-bg-color-green",
- YELLOW_B -> "ansi-bg-color-yellow",
- BLUE_B -> "ansi-bg-color-blue",
+ BLACK -> "ansi-color-black",
+ RED -> "ansi-color-red",
+ GREEN -> "ansi-color-green",
+ YELLOW -> "ansi-color-yellow",
+ BLUE -> "ansi-color-blue",
+ MAGENTA -> "ansi-color-magenta",
+ CYAN -> "ansi-color-cyan",
+ WHITE -> "ansi-color-white",
+ BLACK_B -> "ansi-bg-color-black",
+ RED_B -> "ansi-bg-color-red",
+ GREEN_B -> "ansi-bg-color-green",
+ YELLOW_B -> "ansi-bg-color-yellow",
+ BLUE_B -> "ansi-bg-color-blue",
MAGENTA_B -> "ansi-bg-color-magenta",
- CYAN_B -> "ansi-bg-color-cyan",
- WHITE_B -> "ansi-bg-color-white",
- RESET -> "",
- BLINK -> "ansi-blink",
- BOLD -> "ansi-bold",
- REVERSED -> "ansi-reversed",
+ CYAN_B -> "ansi-bg-color-cyan",
+ WHITE_B -> "ansi-bg-color-white",
+ RESET -> "",
+ BLINK -> "ansi-blink",
+ BOLD -> "ansi-bold",
+ REVERSED -> "ansi-reversed",
INVISIBLE -> "ansi-invisible"
)
def formatToHtml(unformatted: String): String = {
unformatted
- .foldLeft("" -> 0) {
- case ((_r, d), c) =>
- val r = _r + c
- val replaced = colors.collectFirst {
- case (ansiCode, replacement) if r.endsWith(ansiCode) =>
- if (ansiCode == RESET) r.replace(ansiCode, "" * d) -> 0
- else r.replace(ansiCode, s"""""") -> (d + 1)
- }
- replaced.getOrElse(r -> d)
+ .foldLeft("" -> 0) { case ((_r, d), c) =>
+ val r = _r + c
+ val replaced = colors.collectFirst {
+ case (ansiCode, replacement) if r.endsWith(ansiCode) =>
+ if (ansiCode == RESET) r.replace(ansiCode, "" * d) -> 0
+ else r.replace(ansiCode, s"""""") -> (d + 1)
+ }
+ replaced.getOrElse(r -> d)
}
._1
}
+
}
diff --git a/client/src/main/scala/com.olegych.scastie.client/ClientMain.scala b/client/src/main/scala/com.olegych.scastie.client/ClientMain.scala
index 82bc734f2..189d76a86 100644
--- a/client/src/main/scala/com.olegych.scastie.client/ClientMain.scala
+++ b/client/src/main/scala/com.olegych.scastie.client/ClientMain.scala
@@ -1,17 +1,16 @@
package com.olegych.scastie.client
import java.util.UUID
+import scala.scalajs.js
+import scala.scalajs.js.{|, UndefOr}
+import scala.scalajs.js.annotation.{JSExport, _}
import com.olegych.scastie.api.SnippetId
import com.olegych.scastie.client.components._
import japgolly.scalajs.react.component.Generic
import japgolly.scalajs.react.extra.router._
import org.scalajs.dom
-import org.scalajs.dom.{HTMLElement, HTMLDivElement, HTMLLinkElement, Node}
-
-import scala.scalajs.js
-import scala.scalajs.js.annotation.{JSExport, _}
-import scala.scalajs.js.{UndefOr, |}
+import org.scalajs.dom.{HTMLDivElement, HTMLElement, HTMLLinkElement, Node}
@js.native
@JSGlobal("ScastieSettings")
@@ -25,20 +24,24 @@ object Exports {
val ScastieMain = com.olegych.scastie.client.ScastieMain
@JSExport
val ClientMain = com.olegych.scastie.client.ScastieClientMain
+
@JSExport
- def Embedded(selector: UndefOr[String | Node], options: UndefOr[EmbeddedOptionsJs]) = ScastieEmbedded.embedded(selector, options)
+ def Embedded(selector: UndefOr[String | Node], options: UndefOr[EmbeddedOptionsJs]) =
+ ScastieEmbedded.embedded(selector, options)
+
@JSExport
def EmbeddedResource(options: UndefOr[EmbeddedResourceOptionsJs]) = ScastieEmbedded.embeddedResource(options)
}
+
/* Entry point for the website
*/
object ScastieMain {
+
@JSExport
def main(): Unit = {
dom.document.body.className = "scastie"
- val container =
- dom.document.createElement("div").asInstanceOf[HTMLDivElement]
+ val container = dom.document.createElement("div").asInstanceOf[HTMLDivElement]
container.className = "scastie"
dom.document.body.appendChild(container)
@@ -54,11 +57,13 @@ object ScastieMain {
()
}
+
}
/* Entry point for Scala.js runtime
*/
object ScastieClientMain {
+
@JSExport
def signal(instrumentations: String, attachedDoms: js.Array[HTMLElement], rawId: String): Unit = {
Global.signal(instrumentations, attachedDoms, rawId)
@@ -68,30 +73,28 @@ object ScastieClientMain {
def error(er: js.Error, rawId: String): Unit = {
Global.error(er, rawId)
}
+
}
/* Entry point for ressource embedding and code embedding
*/
object ScastieEmbedded {
+
def embedded(selector: UndefOr[String | Node], options: UndefOr[EmbeddedOptionsJs]): Unit = {
- val embeddedOptions =
- options.toOption
- .map(EmbeddedOptions.fromJs(Settings.defaultServerUrl))
- .getOrElse(EmbeddedOptions.empty(Settings.defaultServerUrl))
-
- val nodes =
- selector.toOption match {
- case Some(sel) => {
- (sel: Any) match {
- case cssSelector: String =>
- dom.document.querySelectorAll(cssSelector).toList
- case node: Node =>
- List(node)
- }
+ val embeddedOptions = options.toOption
+ .map(EmbeddedOptions.fromJs(Settings.defaultServerUrl))
+ .getOrElse(EmbeddedOptions.empty(Settings.defaultServerUrl))
+
+ val nodes = selector.toOption match {
+ case Some(sel) => {
+ (sel: Any) match {
+ case cssSelector: String => dom.document.querySelectorAll(cssSelector).toList
+ case node: Node => List(node)
}
- case None => List()
}
+ case None => List()
+ }
if (nodes.nonEmpty) {
addStylesheet(embeddedOptions.serverUrl)
@@ -118,16 +121,14 @@ object ScastieEmbedded {
}
def embeddedResource(options: UndefOr[EmbeddedResourceOptionsJs]): Unit = {
- val embeddedOptions =
- options.toOption
- .map(EmbeddedOptions.fromJsRessource(Settings.defaultServerUrl))
- .getOrElse(EmbeddedOptions.empty(Settings.defaultServerUrl))
-
- val container =
- renderScastie(
- embeddedOptions = embeddedOptions,
- snippetId = embeddedOptions.snippetId
- )
+ val embeddedOptions = options.toOption
+ .map(EmbeddedOptions.fromJsRessource(Settings.defaultServerUrl))
+ .getOrElse(EmbeddedOptions.empty(Settings.defaultServerUrl))
+
+ val container = renderScastie(
+ embeddedOptions = embeddedOptions,
+ snippetId = embeddedOptions.snippetId
+ )
embeddedOptions.injectId match {
case Some(id) => {
@@ -146,6 +147,7 @@ object ScastieEmbedded {
}
}
}
+
def addStylesheet(baseUrl: String): Unit = {
val link = dom.document
.createElement("link")
@@ -175,9 +177,10 @@ object ScastieEmbedded {
targetType = None,
tryLibrary = None,
code = None,
- inputs = None,
+ inputs = None
).render.renderIntoDOM(container)
container
}
+
}
diff --git a/client/src/main/scala/com.olegych.scastie.client/ConsoleState.scala b/client/src/main/scala/com.olegych.scastie.client/ConsoleState.scala
index 031f6bea6..1a07df79d 100644
--- a/client/src/main/scala/com.olegych.scastie.client/ConsoleState.scala
+++ b/client/src/main/scala/com.olegych.scastie.client/ConsoleState.scala
@@ -3,18 +3,18 @@ package com.olegych.scastie.client
import play.api.libs.json._
object ConsoleState {
- implicit val formatConsoleState: OFormat[ConsoleState] =
- Json.format[ConsoleState]
+ implicit val formatConsoleState: OFormat[ConsoleState] = Json.format[ConsoleState]
def default: ConsoleState = ConsoleState(
consoleIsOpen = false,
consoleHasUserOutput = false,
userOpenedConsole = false
)
+
}
case class ConsoleState(
- consoleIsOpen: Boolean,
- consoleHasUserOutput: Boolean,
- userOpenedConsole: Boolean
+ consoleIsOpen: Boolean,
+ consoleHasUserOutput: Boolean,
+ userOpenedConsole: Boolean
)
diff --git a/client/src/main/scala/com.olegych.scastie.client/EmbeddedOptions.scala b/client/src/main/scala/com.olegych.scastie.client/EmbeddedOptions.scala
index e9eba5770..2ca33fc8c 100644
--- a/client/src/main/scala/com.olegych.scastie.client/EmbeddedOptions.scala
+++ b/client/src/main/scala/com.olegych.scastie.client/EmbeddedOptions.scala
@@ -1,10 +1,10 @@
package com.olegych.scastie.client
-import com.olegych.scastie.api.{Inputs, SnippetId, SnippetUserPart, ScalaTarget, ScalaTargetType}
-
import scala.scalajs.js
import scala.scalajs.js.UndefOr
+import com.olegych.scastie.api.{Inputs, ScalaTarget, ScalaTargetType, SnippetId, SnippetUserPart}
+
trait SharedEmbeddedOptions extends js.Object {
val serverUrl: UndefOr[String]
val theme: UndefOr[String]
@@ -33,19 +33,23 @@ trait EmbeddedOptionsJs extends js.Object with SharedEmbeddedOptions {
// val scalaNativeVersion: UndefOr[String] not yet supported
}
-case class EmbeddedOptions(snippetId: Option[SnippetId],
- injectId: Option[String],
- inputs: Option[Inputs],
- theme: Option[String],
- serverUrl: String) {
+case class EmbeddedOptions(
+ snippetId: Option[SnippetId],
+ injectId: Option[String],
+ inputs: Option[Inputs],
+ theme: Option[String],
+ serverUrl: String
+) {
def setCode(code: String): EmbeddedOptions = {
val inputs0 = inputs.getOrElse(Inputs.default)
copy(inputs = Some(inputs0.copy(code = code)))
}
+
}
object EmbeddedOptions {
+
def empty(defaultServerUrl: String): EmbeddedOptions = {
EmbeddedOptions(
snippetId = None,
@@ -57,22 +61,21 @@ object EmbeddedOptions {
}
private def extractSnippetId(
- options: SharedEmbeddedOptions
+ options: SharedEmbeddedOptions
): Option[SnippetId] = {
import options._
- base64UUID.toOption.map(
- uuid =>
- SnippetId(
- uuid,
- user.toOption
- .map(u => SnippetUserPart(u, update.toOption.getOrElse(0)))
+ base64UUID.toOption.map(uuid =>
+ SnippetId(
+ uuid,
+ user.toOption
+ .map(u => SnippetUserPart(u, update.toOption.getOrElse(0)))
)
)
}
def fromJsRessource(
- defaultServerUrl: String
+ defaultServerUrl: String
)(options: EmbeddedResourceOptionsJs): EmbeddedOptions = {
import options._
@@ -95,87 +98,85 @@ object EmbeddedOptions {
}
def fromJs(
- defaultServerUrl: String
+ defaultServerUrl: String
)(options: EmbeddedOptionsJs): EmbeddedOptions = {
import options._
- val scalaTarget =
- (targetType.toOption,
- scalaVersion.toOption,
- None: Option[String], // scalaJsVersion.toOption,
- None: Option[String] // scalaNativeVersion.toOption
- ) match {
-
- case (Some("jvm"), _, None, None) => {
- Some(
- scalaVersion
- .map(version => ScalaTarget.Jvm(version))
- .getOrElse(ScalaTarget.Jvm.default)
- )
- }
-
- case (Some("dotty" | "scala3"), _, None, None) => {
- Some(
- scalaVersion
- .map(version => ScalaTarget.Scala3(version))
- .getOrElse(ScalaTarget.Scala3.default)
- )
- }
-
- case (Some("typelevel"), _, None, None) => {
- Some(
- scalaVersion
- .map(version => ScalaTarget.Typelevel(version))
- .getOrElse(ScalaTarget.Typelevel.default)
- )
- }
-
- case (Some("js"), None, None, None) => {
- Some(ScalaTarget.Js.default)
- }
-
- case (tpe, Some(scalaV), Some(jsV), None) if (tpe.contains("js") || tpe.isEmpty) => {
-
- Some(ScalaTarget.Js(scalaV, jsV))
- }
-
- case (Some("native"), None, None, None) => {
- Some(ScalaTarget.Native.default)
- }
-
- case (tpe, Some(scalaV), None, Some(nativeV)) if (tpe.contains("native") || tpe.isEmpty) => {
- Some(ScalaTarget.Native(scalaV, nativeV))
- }
-
- case (None, None, None, None) => None
-
- case (a, b, c, d) => {
- sys.error(
- s"invalid scala target combination: $a | $b | $c | $d"
- )
- }
+ val scalaTarget = (
+ targetType.toOption,
+ scalaVersion.toOption,
+ None: Option[String], // scalaJsVersion.toOption,
+ None: Option[String] // scalaNativeVersion.toOption
+ ) match {
+
+ case (Some("jvm"), _, None, None) => {
+ Some(
+ scalaVersion
+ .map(version => ScalaTarget.Jvm(version))
+ .getOrElse(ScalaTarget.Jvm.default)
+ )
+ }
+
+ case (Some("dotty" | "scala3"), _, None, None) => {
+ Some(
+ scalaVersion
+ .map(version => ScalaTarget.Scala3(version))
+ .getOrElse(ScalaTarget.Scala3.default)
+ )
+ }
+
+ case (Some("typelevel"), _, None, None) => {
+ Some(
+ scalaVersion
+ .map(version => ScalaTarget.Typelevel(version))
+ .getOrElse(ScalaTarget.Typelevel.default)
+ )
+ }
+
+ case (Some("js"), None, None, None) => {
+ Some(ScalaTarget.Js.default)
+ }
+
+ case (tpe, Some(scalaV), Some(jsV), None) if (tpe.contains("js") || tpe.isEmpty) => {
+
+ Some(ScalaTarget.Js(scalaV, jsV))
+ }
+
+ case (Some("native"), None, None, None) => {
+ Some(ScalaTarget.Native.default)
+ }
+
+ case (tpe, Some(scalaV), None, Some(nativeV)) if (tpe.contains("native") || tpe.isEmpty) => {
+ Some(ScalaTarget.Native(scalaV, nativeV))
}
+ case (None, None, None, None) => None
+
+ case (a, b, c, d) => {
+ sys.error(
+ s"invalid scala target combination: $a | $b | $c | $d"
+ )
+ }
+ }
+
val inputs =
if (scalaTarget.isDefined || code.isDefined) {
val default = Inputs.default
- val isScala3 =
- scalaTarget
- .map(_.targetType == ScalaTargetType.Scala3)
- .getOrElse(false)
+ val isScala3 = scalaTarget
+ .map(_.targetType == ScalaTargetType.Scala3)
+ .getOrElse(false)
val defaultCode =
if (isScala3) ScalaTarget.Scala3.defaultCode
else default.code
- val inputs0 =
- default.copy(
- _isWorksheetMode = isWorksheetMode.getOrElse(default.isWorksheetMode),
- code = code.getOrElse(defaultCode),
- target = scalaTarget.getOrElse(default.target),
- sbtConfigExtra = sbtConfig.getOrElse(default.sbtConfigExtra)
- )
+ val inputs0 = default.copy(
+ _isWorksheetMode = isWorksheetMode.getOrElse(default.isWorksheetMode),
+ code = code.getOrElse(defaultCode),
+ target = scalaTarget.getOrElse(default.target),
+ sbtConfigExtra = sbtConfig.getOrElse(default.sbtConfigExtra)
+ )
Some(inputs0)
} else {
None
@@ -197,4 +198,5 @@ object EmbeddedOptions {
serverUrl = serverUrl.toOption.getOrElse(defaultServerUrl)
)
}
+
}
diff --git a/client/src/main/scala/com.olegych.scastie.client/EventStream.scala b/client/src/main/scala/com.olegych.scastie.client/EventStream.scala
index 5cdda64fb..e5e45d0e5 100644
--- a/client/src/main/scala/com.olegych.scastie.client/EventStream.scala
+++ b/client/src/main/scala/com.olegych.scastie.client/EventStream.scala
@@ -1,19 +1,19 @@
package com.olegych.scastie.client
+import scala.util.Failure
+import scala.util.Success
+
import japgolly.scalajs.react.Callback
import japgolly.scalajs.react.CallbackTo
+import org.scalajs.dom.window
import org.scalajs.dom.CloseEvent
import org.scalajs.dom.Event
import org.scalajs.dom.EventSource
import org.scalajs.dom.MessageEvent
import org.scalajs.dom.WebSocket
-import org.scalajs.dom.window
import play.api.libs.json.Json
import play.api.libs.json.Reads
-import scala.util.Failure
-import scala.util.Success
-
abstract class EventStream[T: Reads](handler: EventStreamHandler[T]) {
var closing = false
@@ -27,8 +27,9 @@ abstract class EventStream[T: Reads](handler: EventStreamHandler[T]) {
}
}
}
- def onOpen(): Unit = handler.onOpen()
- def onError(error: String): Unit = handler.onError(error)
+
+ def onOpen(): Unit = handler.onOpen()
+ def onError(error: String): Unit = handler.onError(error)
def onClose(reason: Option[String]): Unit = handler.onClose(reason)
def close(force: Boolean = false): Unit = {
@@ -37,6 +38,7 @@ abstract class EventStream[T: Reads](handler: EventStreamHandler[T]) {
onClose(None)
}
}
+
}
trait EventStreamHandler[T] {
@@ -50,17 +52,16 @@ trait EventStreamHandler[T] {
}
object EventStream {
+
def connect[T: Reads](eventSourceUri: String, websocketUri: String, handler: EventStreamHandler[T]): Callback = {
- def connectEventSource =
- CallbackTo[EventStream[T]](
- new EventSourceStream(eventSourceUri, handler)
- )
+ def connectEventSource = CallbackTo[EventStream[T]](
+ new EventSourceStream(eventSourceUri, handler)
+ )
- def connectWebSocket =
- CallbackTo[EventStream[T]](
- new WebSocketStream(websocketUri, handler)
- )
+ def connectWebSocket = CallbackTo[EventStream[T]](
+ new WebSocketStream(websocketUri, handler)
+ )
connectEventSource.attemptTry.flatMap {
case Success(eventSource) => {
@@ -79,6 +80,7 @@ object EventStream {
}
}
}
+
}
class WebSocketStream[T: Reads](uri: String, handler: EventStreamHandler[T]) extends EventStream[T](handler) {
@@ -100,9 +102,8 @@ class WebSocketStream[T: Reads](uri: String, handler: EventStreamHandler[T]) ext
socket.close()
}
- val protocol: String =
- if (window.location.protocol == "https:") "wss" else "ws"
- val fullUri: String = s"$protocol://${window.location.host}${uri}"
+ val protocol: String = if (window.location.protocol == "https:") "wss" else "ws"
+ val fullUri: String = s"$protocol://${window.location.host}${uri}"
val socket: WebSocket = new WebSocket(uri)
socket.onopen = onOpen _
diff --git a/client/src/main/scala/com.olegych.scastie.client/Global.scala b/client/src/main/scala/com.olegych.scastie.client/Global.scala
index 1fb150b40..6ffc96a44 100644
--- a/client/src/main/scala/com.olegych.scastie.client/Global.scala
+++ b/client/src/main/scala/com.olegych.scastie.client/Global.scala
@@ -1,20 +1,16 @@
package com.olegych.scastie.client
-import com.olegych.scastie.api._
-import com.olegych.scastie.client.components.Scastie
-
-import scala.scalajs.js
+import java.util.UUID
import scala.collection.mutable.{Map => MMap}
-import scala.util.{Try, Failure, Success}
-
-import org.scalajs.dom.HTMLElement
+import scala.scalajs.js
+import scala.util.{Failure, Success, Try}
+import com.olegych.scastie.api._
+import com.olegych.scastie.client.components.Scastie
import japgolly.scalajs.react._
-
+import org.scalajs.dom.HTMLElement
import play.api.libs.json.Json
-import java.util.UUID
-
object Global {
type Scope = BackendScope[Scastie, ScastieState]
@@ -30,33 +26,31 @@ object Global {
def error(er: js.Error, rawId: String): Unit = {
withScope(rawId)(
- _.withEffectsImpure.modState(
- state =>
- state
- .copyAndSave(
- outputs = state.outputs.copy(
- runtimeError = Some(
- RuntimeError(
- message = er.toString,
- line = None,
- fullStack = ""
- )
+ _.withEffectsImpure.modState(state =>
+ state
+ .copyAndSave(
+ outputs = state.outputs.copy(
+ runtimeError = Some(
+ RuntimeError(
+ message = er.toString,
+ line = None,
+ fullStack = ""
)
)
)
- .setRunning(false)
+ )
+ .setRunning(false)
)
)
}
def signal(instrumentationsRaw: String, attachedDoms: js.Array[HTMLElement], rawId: String): Unit = {
- val result =
- Json
- .fromJson[ScalaJsResult](
- Json.parse(instrumentationsRaw)
- )
- .asOpt
+ val result = Json
+ .fromJson[ScalaJsResult](
+ Json.parse(instrumentationsRaw)
+ )
+ .asOpt
val (instr, runtimeError) = result.map(_.in) match {
case Some(Left(maybeRuntimeError)) => (Nil, maybeRuntimeError)
@@ -65,18 +59,17 @@ object Global {
}
withScope(rawId)(
- _.withEffectsImpure.modState(
- state =>
- state
- .copyAndSave(
- outputs = state.outputs.copy(
- instrumentations = state.outputs.instrumentations ++ instr.toSet,
- runtimeError = runtimeError
- )
+ _.withEffectsImpure.modState(state =>
+ state
+ .copyAndSave(
+ outputs = state.outputs.copy(
+ instrumentations = state.outputs.instrumentations ++ instr.toSet,
+ runtimeError = runtimeError
)
- .setRunning(false)
- .copy(
- attachedDoms = attachedDoms.map(dom => (dom.getAttribute("uuid"), dom)).toMap
+ )
+ .setRunning(false)
+ .copy(
+ attachedDoms = attachedDoms.map(dom => (dom.getAttribute("uuid"), dom)).toMap
)
)
)
@@ -92,4 +85,5 @@ object Global {
case Failure(e) => e.printStackTrace()
}
}
+
}
diff --git a/client/src/main/scala/com.olegych.scastie.client/HTMLFormatter.scala b/client/src/main/scala/com.olegych.scastie.client/HTMLFormatter.scala
index 106e16f3f..f230aee5e 100644
--- a/client/src/main/scala/com.olegych.scastie.client/HTMLFormatter.scala
+++ b/client/src/main/scala/com.olegych.scastie.client/HTMLFormatter.scala
@@ -1,22 +1,21 @@
package com.olegych.scastie.client
object HTMLFormatter {
- private val escapeMap =
- Map('&' -> "&", '"' -> """, '<' -> "<", '>' -> ">")
+ private val escapeMap = Map('&' -> "&", '"' -> """, '<' -> "<", '>' -> ">")
- private def escape(text: String): String =
- text.iterator
- .foldLeft(new StringBuilder()) { (s, c) =>
- escapeMap.get(c) match {
- case Some(str) => s ++= str
- case _ if c >= ' ' || "\n\r\t\u001b".contains(c) => s += c
- case _ => s // noop
- }
+ private def escape(text: String): String = text.iterator
+ .foldLeft(new StringBuilder()) { (s, c) =>
+ escapeMap.get(c) match {
+ case Some(str) => s ++= str
+ case _ if c >= ' ' || "\n\r\t\u001b".contains(c) => s += c
+ case _ => s // noop
}
- .toString
+ }
+ .toString
def format(notEscapedAndUnformatted: String) = {
val escaped = escape(notEscapedAndUnformatted)
AnsiColorFormatter.formatToHtml(escaped)
}
+
}
diff --git a/client/src/main/scala/com.olegych.scastie.client/LocalStorage.scala b/client/src/main/scala/com.olegych.scastie.client/LocalStorage.scala
index 9d3b64f78..3866e566a 100644
--- a/client/src/main/scala/com.olegych.scastie.client/LocalStorage.scala
+++ b/client/src/main/scala/com.olegych.scastie.client/LocalStorage.scala
@@ -1,10 +1,9 @@
package com.olegych.scastie
package client
-import play.api.libs.json.Json
-
import org.scalajs.dom
import org.scalajs.dom.window.localStorage
+import play.api.libs.json.Json
object LocalStorage {
private val stateKey = "state"
@@ -23,4 +22,5 @@ object LocalStorage {
None
}
}
+
}
diff --git a/client/src/main/scala/com.olegych.scastie.client/ModalState.scala b/client/src/main/scala/com.olegych.scastie.client/ModalState.scala
index 461e3601e..5c428cc7a 100644
--- a/client/src/main/scala/com.olegych.scastie.client/ModalState.scala
+++ b/client/src/main/scala/com.olegych.scastie.client/ModalState.scala
@@ -1,10 +1,8 @@
package com.olegych.scastie.client
import com.olegych.scastie.api.SnippetId
-
-import play.api.libs.json._
-
import japgolly.scalajs.react._
+import play.api.libs.json._
object ModalState {
implicit val formatModalState: OFormat[ModalState] = Json.format[ModalState]
@@ -30,22 +28,21 @@ object ModalState {
isEmbeddedClosed = true,
isLoginModalClosed = true
)
+
}
case class ModalState(
- isHelpModalClosed: Boolean,
- isPrivacyPolicyModalClosed: Boolean,
- @deprecated("Scheduled for removal", "2023-04-30")
- isPrivacyPolicyPromptClosed: Boolean,
- shareModalSnippetId: Option[SnippetId],
- isResetModalClosed: Boolean,
- isNewSnippetModalClosed: Boolean,
- isEmbeddedClosed: Boolean,
- isLoginModalClosed: Boolean
+ isHelpModalClosed: Boolean,
+ isPrivacyPolicyModalClosed: Boolean,
+ @deprecated("Scheduled for removal", "2023-04-30")
+ isPrivacyPolicyPromptClosed: Boolean,
+ shareModalSnippetId: Option[SnippetId],
+ isResetModalClosed: Boolean,
+ isNewSnippetModalClosed: Boolean,
+ isEmbeddedClosed: Boolean,
+ isLoginModalClosed: Boolean
) {
val isShareModalClosed: SnippetId ~=> Boolean =
- Reusable.fn(
- shareModalSnippetId2 => !shareModalSnippetId.contains(shareModalSnippetId2)
- )
+ Reusable.fn(shareModalSnippetId2 => !shareModalSnippetId.contains(shareModalSnippetId2))
}
diff --git a/client/src/main/scala/com.olegych.scastie.client/RestApiClient.scala b/client/src/main/scala/com.olegych.scastie.client/RestApiClient.scala
index 8f5235705..6557e2a4f 100644
--- a/client/src/main/scala/com.olegych.scastie.client/RestApiClient.scala
+++ b/client/src/main/scala/com.olegych.scastie.client/RestApiClient.scala
@@ -1,41 +1,40 @@
package com.olegych.scastie.client
+import scala.concurrent.Future
+import scala.util.Try
+
import com.olegych.scastie.api._
import org.scalajs.dom
import org.scalajs.dom.XMLHttpRequest
import play.api.libs.json._
-
-import scala.concurrent.Future
-import scala.util.Try
-
import scalajs.concurrent.JSExecutionContext.Implicits.queue
-import scalajs.js.Thenable.Implicits._
import scalajs.js
+import scalajs.js.Thenable.Implicits._
class RestApiClient(serverUrl: Option[String]) extends RestApi {
val apiBase: String = serverUrl.getOrElse("")
- def tryParse[T: Reads](response: XMLHttpRequest): Option[T] =
- tryParse(response.responseText)
+ def tryParse[T: Reads](response: XMLHttpRequest): Option[T] = tryParse(response.responseText)
- def tryParse[T: Reads](response: dom.Response): Future[Option[T]] =
- response.text().map(tryParse(_))
+ def tryParse[T: Reads](response: dom.Response): Future[Option[T]] = response.text().map(tryParse(_))
def tryParse[T: Reads](text: String): Option[T] = {
- Option.when(text.nonEmpty)(text).flatMap(t =>
- Try(Json.parse(t)).toOption.flatMap(Json.fromJson[T](_).asOpt)
- )
+ Option.when(text.nonEmpty)(text).flatMap(t => Try(Json.parse(t)).toOption.flatMap(Json.fromJson[T](_).asOpt))
}
def get[T: Reads](url: String): Future[Option[T]] = {
val header = new dom.Headers(js.Dictionary("Accept" -> "application/json"))
dom
- .fetch(apiBase + "/api" + url, js.Dynamic.literal(headers = header, method = dom.HttpMethod.GET).asInstanceOf[dom.RequestInit])
+ .fetch(
+ apiBase + "/api" + url,
+ js.Dynamic.literal(headers = header, method = dom.HttpMethod.GET).asInstanceOf[dom.RequestInit]
+ )
.flatMap(tryParse[T](_))
}
class Post[O: Reads]() {
+
def using[I: Writes](url: String, data: I, async: Boolean = true): Future[Option[O]] = {
val header = new dom.Headers(js.Dictionary("Accept" -> "application/json", "Content-Type" -> "application/json"))
dom
@@ -47,18 +46,16 @@ class RestApiClient(serverUrl: Option[String]) extends RestApi {
)
.flatMap(tryParse[O](_))
}
+
}
def post[O: Reads]: Post[O] = new Post[O]
- def run(inputs: Inputs): Future[SnippetId] =
- post[SnippetId].using("/run", inputs).map(_.get)
+ def run(inputs: Inputs): Future[SnippetId] = post[SnippetId].using("/run", inputs).map(_.get)
- def format(request: FormatRequest): Future[FormatResponse] =
- post[FormatResponse].using("/format", request).map(_.get)
+ def format(request: FormatRequest): Future[FormatResponse] = post[FormatResponse].using("/format", request).map(_.get)
- def save(inputs: Inputs): Future[SnippetId] =
- post[SnippetId].using("/save", inputs).map(_.get)
+ def save(inputs: Inputs): Future[SnippetId] = post[SnippetId].using("/save", inputs).map(_.get)
def saveBlocking(inputs: Inputs): Option[SnippetId] = {
val req = new dom.XMLHttpRequest()
@@ -83,23 +80,17 @@ class RestApiClient(serverUrl: Option[String]) extends RestApi {
snippetId
}
- def update(editInputs: EditInputs): Future[Option[SnippetId]] =
- post[SnippetId].using("/update", editInputs)
+ def update(editInputs: EditInputs): Future[Option[SnippetId]] = post[SnippetId].using("/update", editInputs)
- def fork(editInputs: EditInputs): Future[Option[SnippetId]] =
- post[SnippetId].using("/fork", editInputs)
+ def fork(editInputs: EditInputs): Future[Option[SnippetId]] = post[SnippetId].using("/fork", editInputs)
- def delete(snippetId: SnippetId): Future[Boolean] =
- post[Boolean].using("/delete", snippetId).map(_.getOrElse(false))
+ def delete(snippetId: SnippetId): Future[Boolean] = post[Boolean].using("/delete", snippetId).map(_.getOrElse(false))
- def fetch(snippetId: SnippetId): Future[Option[FetchResult]] =
- get[FetchResult]("/snippets/" + snippetId.url)
+ def fetch(snippetId: SnippetId): Future[Option[FetchResult]] = get[FetchResult]("/snippets/" + snippetId.url)
- def fetchOld(id: Int): Future[Option[FetchResult]] =
- get[FetchResult](s"/old-snippets/$id")
+ def fetchOld(id: Int): Future[Option[FetchResult]] = get[FetchResult](s"/old-snippets/$id")
- def fetchUser(): Future[Option[User]] =
- get[User]("/user/settings")
+ def fetchUser(): Future[Option[User]] = get[User]("/user/settings")
@deprecated("Scheduled for removal", "2023-04-30")
def getPrivacyPolicyStatus(): Future[Boolean] =
@@ -107,15 +98,15 @@ class RestApiClient(serverUrl: Option[String]) extends RestApi {
@deprecated("Scheduled for removal", "2023-04-30")
def acceptPrivacyPolicy(): Future[Boolean] =
- post[Boolean].using("/user/acceptPrivacyPolicy", "", async=false).map(_.getOrElse(false))
+ post[Boolean].using("/user/acceptPrivacyPolicy", "", async = false).map(_.getOrElse(false))
@deprecated("Scheduled for removal", "2023-04-30")
def removeAllUserSnippets(): Future[Boolean] =
- post[Boolean].using("/user/removeAllUserSnippets", "", async=false).map(_.getOrElse(false))
+ post[Boolean].using("/user/removeAllUserSnippets", "", async = false).map(_.getOrElse(false))
@deprecated("Scheduled for removal", "2023-04-30")
def removeUserFromPolicyStatus(): Future[Boolean] =
- post[Boolean].using("/user/removeUserFromPolicyStatus", "", async=false).map(_.getOrElse(false))
+ post[Boolean].using("/user/removeUserFromPolicyStatus", "", async = false).map(_.getOrElse(false))
def fetchUserSnippets(): Future[List[SnippetSummary]] =
get[List[SnippetSummary]]("/user/snippets").map(_.getOrElse(Nil))
diff --git a/client/src/main/scala/com.olegych.scastie.client/Routing.scala b/client/src/main/scala/com.olegych.scastie.client/Routing.scala
index 0c31a099a..bb90dce35 100644
--- a/client/src/main/scala/com.olegych.scastie.client/Routing.scala
+++ b/client/src/main/scala/com.olegych.scastie.client/Routing.scala
@@ -1,28 +1,28 @@
package com.olegych.scastie.client
+import java.util.UUID
+
import com.olegych.scastie.api.{Inputs, Project, ScalaDependency, ScalaTarget, ScalaTargetType, ScalaVersions, SnippetId, SnippetUserPart}
import com.olegych.scastie.client.components._
-import japgolly.scalajs.react._
-import vdom.all._
import extra.router._
+import japgolly.scalajs.react._
import play.api.libs.json.Json
-
-import java.util.UUID
+import vdom.all._
class Routing(defaultServerUrl: String) {
+
val config: RouterConfig[Page] = RouterConfigDsl[Page].buildConfig { dsl =>
import dsl._
- val embedded = "embedded"
- val alpha = string("[a-zA-Z0-9-]+")
+ val embedded = "embedded"
+ val alpha = string("[a-zA-Z0-9-]+")
val snippetId = string("[a-zA-Z0-9-]{22}")
val targetType = queryToMap.pmap { map =>
(
map.get("target"),
- map.get("c"),
+ map.get("c")
) match {
- case (Some(target), c) =>
- ScalaTargetType.parse(target.toUpperCase).map(target => TargetTypePage(target, c))
- case _ => None
+ case (Some(target), c) => ScalaTargetType.parse(target.toUpperCase).map(target => TargetTypePage(target, c))
+ case _ => None
}
}(p => Map("target" -> p.targetType.toString) ++ p.code.map("c" -> _))
@@ -30,11 +30,14 @@ class Routing(defaultServerUrl: String) {
map.get("inputs").flatMap { inputs =>
Json
.fromJson[Inputs](Json.parse(inputs))
- .fold({ e =>
- println(s"failed to parse ${inputs}")
- println(e)
- None
- }, inputs => Some(InputsPage(inputs)))
+ .fold(
+ { e =>
+ println(s"failed to parse ${inputs}")
+ println(e)
+ None
+ },
+ inputs => Some(InputsPage(inputs))
+ )
}
}(p => Map("inputs" -> Json.toJson(p.inputs).toString().replace("{", "%7B").replace("}", "%7D")))
@@ -45,14 +48,12 @@ class Routing(defaultServerUrl: String) {
map.get("v"),
map.get("o"),
map.get("r"),
- map.get("c"),
+ map.get("c")
) match {
case (Some(g), Some(a), Some(v), o, r, c) =>
val target = map.get("t").flatMap(ScalaTargetType.parse) match {
- case Some(t @ ScalaTargetType.Scala2) =>
- map.get("sv").map(sv => ScalaTarget.Jvm(ScalaVersions.find(t, sv)))
- case Some(t @ ScalaTargetType.JS) =>
- (map.get("sv"), map.get("sjsv")) match {
+ case Some(t @ ScalaTargetType.Scala2) => map.get("sv").map(sv => ScalaTarget.Jvm(ScalaVersions.find(t, sv)))
+ case Some(t @ ScalaTargetType.JS) => (map.get("sv"), map.get("sjsv")) match {
case (Some(sv), sjsv) =>
Some(ScalaTarget.Js(ScalaVersions.find(t, sv), sjsv.getOrElse(ScalaTarget.Js.default.scalaJsVersion)))
case _ => None
@@ -79,45 +80,45 @@ class Routing(defaultServerUrl: String) {
"a" -> dep.dependency.artifact,
"v" -> dep.dependency.version,
"r" -> dep.project.repository,
- "o" -> dep.project.organization,
+ "o" -> dep.project.organization
)
}
val tryLibrary = queryToMap.pmap(parseTryLibrary)(renderTryLibrary)
- val anon = snippetId
- val user = alpha / snippetId
+ val anon = snippetId
+ val user = alpha / snippetId
val userUpdate = alpha / snippetId / int
- val oldId = int
+ val oldId = int
(
trimSlashes
| staticRoute(root, Home) ~>
- renderR(renderScastieDefault)
+ renderR(renderScastieDefault)
| dynamicRouteCT("try" ~ tryLibrary) ~>
- dynRenderR((page, router) => renderTryLibraryPage(page, router))
+ dynRenderR((page, router) => renderTryLibraryPage(page, router))
| dynamicRouteCT(inputs) ~>
- dynRenderR((page, router) => renderInputs(page, router))
+ dynRenderR((page, router) => renderInputs(page, router))
| dynamicRouteCT(targetType) ~>
- dynRenderR((page, router) => renderTargetTypePage(page, router))
+ dynRenderR((page, router) => renderTargetTypePage(page, router))
| dynamicRouteCT(oldId.caseClass[OldSnippetIdPage]) ~>
- dynRenderR((page, router) => renderOldSnippetIdPage(page, router))
+ dynRenderR((page, router) => renderOldSnippetIdPage(page, router))
| dynamicRouteCT(anon.caseClass[AnonymousResource]) ~>
- dynRenderR((page, router) => renderPage(page, router))
+ dynRenderR((page, router) => renderPage(page, router))
| dynamicRouteCT(user.caseClass[UserResource]) ~>
- dynRenderR((page, router) => renderPage(page, router))
+ dynRenderR((page, router) => renderPage(page, router))
| dynamicRouteCT(userUpdate.caseClass[UserResourceUpdated]) ~>
- dynRenderR((page, router) => renderPage(page, router))
+ dynRenderR((page, router) => renderPage(page, router))
| staticRoute(embedded, Embedded) ~>
- renderR(renderScastieDefaultEmbedded)
+ renderR(renderScastieDefaultEmbedded)
| dynamicRouteCT(embedded / anon.caseClass[EmbeddedAnonymousResource]) ~>
- dynRenderR((page, router) => renderPage(page, router))
+ dynRenderR((page, router) => renderPage(page, router))
| dynamicRouteCT(embedded / user.caseClass[EmbeddedUserResource]) ~>
- dynRenderR((page, router) => renderPage(page, router))
+ dynRenderR((page, router) => renderPage(page, router))
| dynamicRouteCT(
embedded / userUpdate.caseClass[EmbeddedUserResourceUpdated]
) ~>
- dynRenderR((page, router) => renderPage(page, router))
+ dynRenderR((page, router) => renderPage(page, router))
).notFound(redirectToPage(Home)(SetRouteVia.HistoryReplace))
.renderWith((page, router) => layout(page, router))
}
@@ -144,12 +145,11 @@ class Routing(defaultServerUrl: String) {
}
private def renderScastieDefaultEmbedded(
- router: RouterCtl[Page]
- ): VdomElement =
- Scastie
- .default(router)
- .copy(embedded = Some(EmbeddedOptions.empty(defaultServerUrl)))
- .render
+ router: RouterCtl[Page]
+ ): VdomElement = Scastie
+ .default(router)
+ .copy(embedded = Some(EmbeddedOptions.empty(defaultServerUrl)))
+ .render
private def renderPage(page: ResourcePage, router: RouterCtl[Page]): VdomElement = {
val defaultEmbedded = Some(EmbeddedOptions.empty(defaultServerUrl))
@@ -184,7 +184,7 @@ class Routing(defaultServerUrl: String) {
targetType = None,
tryLibrary = None,
code = None,
- inputs = None,
+ inputs = None
).render
}
diff --git a/client/src/main/scala/com.olegych.scastie.client/RoutingADT.scala b/client/src/main/scala/com.olegych.scastie.client/RoutingADT.scala
index 0442b1da3..8ccaaf3e5 100644
--- a/client/src/main/scala/com.olegych.scastie.client/RoutingADT.scala
+++ b/client/src/main/scala/com.olegych.scastie.client/RoutingADT.scala
@@ -3,57 +3,56 @@ package com.olegych.scastie.client
import com.olegych.scastie.api._
object Page {
+
def fromSnippetId(snippetId: SnippetId): ResourcePage = {
snippetId match {
- case SnippetId(uuid, None) =>
- AnonymousResource(uuid)
+ case SnippetId(uuid, None) => AnonymousResource(uuid)
- case SnippetId(uuid, Some(SnippetUserPart(login, 0))) =>
- UserResource(login, uuid)
+ case SnippetId(uuid, Some(SnippetUserPart(login, 0))) => UserResource(login, uuid)
- case SnippetId(uuid, Some(SnippetUserPart(login, update))) =>
- UserResourceUpdated(login, uuid, update)
+ case SnippetId(uuid, Some(SnippetUserPart(login, update))) => UserResourceUpdated(login, uuid, update)
}
}
+
}
sealed trait Page
-case object Home extends Page
+case object Home extends Page
case object Embedded extends Page
-case class TargetTypePage(targetType: ScalaTargetType, code: Option[String]) extends Page
+case class TargetTypePage(targetType: ScalaTargetType, code: Option[String]) extends Page
case class TryLibraryPage(dependency: ScalaDependency, project: Project, code: Option[String]) extends Page
-case class OldSnippetIdPage(id: Int) extends Page
-case class InputsPage(inputs: Inputs) extends Page
+case class OldSnippetIdPage(id: Int) extends Page
+case class InputsPage(inputs: Inputs) extends Page
sealed trait ResourcePage extends Page
case class AnonymousResource(
- uuid: String
+ uuid: String
) extends ResourcePage
case class UserResource(
- login: String,
- uuid: String
+ login: String,
+ uuid: String
) extends ResourcePage
case class UserResourceUpdated(
- login: String,
- uuid: String,
- update: Int
+ login: String,
+ uuid: String,
+ update: Int
) extends ResourcePage
case class EmbeddedAnonymousResource(
- uuid: String
+ uuid: String
) extends ResourcePage
case class EmbeddedUserResource(
- login: String,
- uuid: String
+ login: String,
+ uuid: String
) extends ResourcePage
case class EmbeddedUserResourceUpdated(
- login: String,
- uuid: String,
- update: Int
+ login: String,
+ uuid: String,
+ update: Int
) extends ResourcePage
diff --git a/client/src/main/scala/com.olegych.scastie.client/ScastieBackend.scala b/client/src/main/scala/com.olegych.scastie.client/ScastieBackend.scala
index 3b503dded..8b701be72 100644
--- a/client/src/main/scala/com.olegych.scastie.client/ScastieBackend.scala
+++ b/client/src/main/scala/com.olegych.scastie.client/ScastieBackend.scala
@@ -1,6 +1,8 @@
package com.olegych.scastie.client
import java.util.UUID
+import scala.concurrent.Future
+import scala.scalajs.concurrent.JSExecutionContext.Implicits.queue
import com.olegych.scastie.api._
import com.olegych.scastie.client.components.Scastie
@@ -10,13 +12,9 @@ import japgolly.scalajs.react.extra._
import japgolly.scalajs.react.util.Effect.Id
import org.scalajs.dom.{Position => _, _}
-import scala.concurrent.Future
-import scala.scalajs.concurrent.JSExecutionContext.Implicits.queue
-
case class ScastieBackend(scastieId: UUID, serverUrl: Option[String], scope: BackendScope[Scastie, ScastieState]) {
- private val restApiClient =
- new RestApiClient(serverUrl)
+ private val restApiClient = new RestApiClient(serverUrl)
// XXX: This should not be global
Global.subscribe(scope, scastieId)
@@ -25,131 +23,98 @@ case class ScastieBackend(scastieId: UUID, serverUrl: Option[String], scope: Bac
Callback(Global.subscribe(scope, scastieId))
}
- val codeChange: String ~=> Callback =
- Reusable.fn(code => scope.modState(_.setCode(code)))
+ val codeChange: String ~=> Callback = Reusable.fn(code => scope.modState(_.setCode(code)))
val sbtConfigChange: String ~=> Callback = {
Reusable.fn(newConfig => scope.modState(_.setSbtConfigExtra(newConfig)))
}
- val resetBuild: Reusable[Callback] =
- Reusable.always {
- val setData = scope.state.map(
- state => {
- state
- .setInputs(Inputs.default.copy(code = state.inputs.code))
- .clearOutputs
- .clearSnippetId
- .setChangedInputs
- }
- )
+ val resetBuild: Reusable[Callback] = Reusable.always {
+ val setData = scope.state.map(state => {
+ state
+ .setInputs(Inputs.default.copy(code = state.inputs.code))
+ .clearOutputs
+ .clearSnippetId
+ .setChangedInputs
+ })
- setData >> setHome
- }
+ setData >> setHome
+ }
- val newSnippet: Reusable[Callback] =
- Reusable.always {
- val setData = scope.state.map(state => {
- state
- .copy(isDesktopForced = false)
- .setInputs(Inputs.default.copy(code = ""))
- .clearOutputs
- .clearSnippetId
- .setChangedInputs
- })
-
- setData >> setHome
- }
+ val newSnippet: Reusable[Callback] = Reusable.always {
+ val setData = scope.state.map(state => {
+ state
+ .copy(isDesktopForced = false)
+ .setInputs(Inputs.default.copy(code = ""))
+ .clearOutputs
+ .clearSnippetId
+ .setChangedInputs
+ })
+
+ setData >> setHome
+ }
val clear: Reusable[Callback] =
Reusable.always(scope.modState(_.clearOutputsPreserveConsole) >> scope.modState(_.closeModals))
- private def clearOutputs: Callback =
- scope.modState(_.clearOutputs)
+ private def clearOutputs: Callback = scope.modState(_.clearOutputs)
- def clearCode: Callback =
- scope.modState(_.setCode(""))
+ def clearCode: Callback = scope.modState(_.setCode(""))
- val setViewReused: View ~=> Callback =
- Reusable.fn(setView _)
+ val setViewReused: View ~=> Callback = Reusable.fn(setView _)
- def setView(newView: View): Callback =
- scope.modState(_.setView(newView))
+ def setView(newView: View): Callback = scope.modState(_.setView(newView))
val viewSnapshot: StateSnapshot.withReuse.FromSetStateFn[View] =
StateSnapshot.withReuse.prepare((opts, c) => opts.fold(c)(setView))
- val setTarget: ScalaTarget ~=> Callback =
- Reusable.fn(target => scope.modState(_.setTarget(target)))
+ val setTarget: ScalaTarget ~=> Callback = Reusable.fn(target => scope.modState(_.setTarget(target)))
- val addScalaDependency: (ScalaDependency, Project) ~=> Callback =
- Reusable.fn {
- case (scalaDependency, project) =>
- scope.modState(_.addScalaDependency(scalaDependency, project))
- }
+ val addScalaDependency: (ScalaDependency, Project) ~=> Callback = Reusable.fn { case (scalaDependency, project) =>
+ scope.modState(_.addScalaDependency(scalaDependency, project))
+ }
val removeScalaDependency: ScalaDependency ~=> Callback =
- Reusable.fn(
- scalaDependency => scope.modState(_.removeScalaDependency(scalaDependency))
- )
+ Reusable.fn(scalaDependency => scope.modState(_.removeScalaDependency(scalaDependency)))
- val updateDependencyVersion: (ScalaDependency, String) ~=> Callback =
- Reusable.fn {
- case (scalaDependency, version) =>
- scope.modState(_.updateDependencyVersion(scalaDependency, version))
- }
+ val updateDependencyVersion: (ScalaDependency, String) ~=> Callback = Reusable.fn { case (scalaDependency, version) =>
+ scope.modState(_.updateDependencyVersion(scalaDependency, version))
+ }
- val toggleTheme: Reusable[Callback] =
- Reusable.always(scope.modState(_.toggleTheme))
+ val toggleTheme: Reusable[Callback] = Reusable.always(scope.modState(_.toggleTheme))
- val setMetalsStatus: MetalsStatus ~=> Callback =
- Reusable.fn(status => scope.modState(_.setMetalsStatus(status)))
+ val setMetalsStatus: MetalsStatus ~=> Callback = Reusable.fn(status => scope.modState(_.setMetalsStatus(status)))
- val toggleMetalsStatus: Reusable[Callback] =
- Reusable.always(scope.modState(_.toggleMetalsStatus))
+ val toggleMetalsStatus: Reusable[Callback] = Reusable.always(scope.modState(_.toggleMetalsStatus))
- val toggleLineNumbers: Reusable[Callback] =
- Reusable.always(scope.modState(_.toggleLineNumbers))
+ val toggleLineNumbers: Reusable[Callback] = Reusable.always(scope.modState(_.toggleLineNumbers))
- val togglePresentationMode: Reusable[Callback] =
- Reusable.always(scope.modState(_.togglePresentationMode))
+ val togglePresentationMode: Reusable[Callback] = Reusable.always(scope.modState(_.togglePresentationMode))
- val openConsole: Reusable[Callback] =
- Reusable.always(scope.modState(_.openConsole))
+ val openConsole: Reusable[Callback] = Reusable.always(scope.modState(_.openConsole))
- val closeConsole: Reusable[Callback] =
- Reusable.always(scope.modState(_.closeConsole))
+ val closeConsole: Reusable[Callback] = Reusable.always(scope.modState(_.closeConsole))
- val toggleConsole: Reusable[Callback] =
- Reusable.always(scope.modState(_.toggleConsole))
+ val toggleConsole: Reusable[Callback] = Reusable.always(scope.modState(_.toggleConsole))
- val openResetModal: Reusable[Callback] =
- Reusable.always(scope.modState(_.openResetModal))
+ val openResetModal: Reusable[Callback] = Reusable.always(scope.modState(_.openResetModal))
- val closeResetModal: Reusable[Callback] =
- Reusable.always(scope.modState(_.closeResetModal))
+ val closeResetModal: Reusable[Callback] = Reusable.always(scope.modState(_.closeResetModal))
- val openNewSnippetModal: Reusable[Callback] =
- Reusable.always(scope.modState(_.openNewSnippetModal))
+ val openNewSnippetModal: Reusable[Callback] = Reusable.always(scope.modState(_.openNewSnippetModal))
// ok
- private def closeNewSnippetModal0: Callback =
- scope.modState(_.closeNewSnippetModal)
+ private def closeNewSnippetModal0: Callback = scope.modState(_.closeNewSnippetModal)
- val closeNewSnippetModal: Reusable[Callback] =
- Reusable.always(closeNewSnippetModal0)
+ val closeNewSnippetModal: Reusable[Callback] = Reusable.always(closeNewSnippetModal0)
- val openHelpModal: Reusable[Callback] =
- Reusable.always(scope.modState(_.openHelpModal))
+ val openHelpModal: Reusable[Callback] = Reusable.always(scope.modState(_.openHelpModal))
- val openPrivacyPolicyModal: Reusable[Callback] =
- Reusable.always(scope.modState(_.openPrivacyPolicyModal))
+ val openPrivacyPolicyModal: Reusable[Callback] = Reusable.always(scope.modState(_.openPrivacyPolicyModal))
- val closeHelpModal: Reusable[Callback] =
- Reusable.always(scope.modState(_.toggleHelpModal))
+ val closeHelpModal: Reusable[Callback] = Reusable.always(scope.modState(_.toggleHelpModal))
- val closePrivacyPolicyModal: Reusable[Callback] =
- Reusable.always(scope.modState(_.togglePrivacyPolicyModal))
+ val closePrivacyPolicyModal: Reusable[Callback] = Reusable.always(scope.modState(_.togglePrivacyPolicyModal))
val closePrivacyPolicyPrompt: Reusable[Callback] =
Reusable.always(scope.modState(_.setPrivacyPolicyPromptClosed(true)))
@@ -157,17 +122,13 @@ case class ScastieBackend(scastieId: UUID, serverUrl: Option[String], scope: Bac
val openPrivacyPolicyPrompt: Reusable[Callback] =
Reusable.always(scope.modState(_.setPrivacyPolicyPromptClosed(false)))
- val openLoginModal: Reusable[Callback] =
- Reusable.always(scope.modState(_.setLoginModalClosed(false)))
+ val openLoginModal: Reusable[Callback] = Reusable.always(scope.modState(_.setLoginModalClosed(false)))
- val closeLoginModal: Reusable[Callback] =
- Reusable.always(scope.modState(_.setLoginModalClosed(true)))
+ val closeLoginModal: Reusable[Callback] = Reusable.always(scope.modState(_.setLoginModalClosed(true)))
- val toggleHelpModal: Reusable[Callback] =
- Reusable.always(scope.modState(_.toggleHelpModal))
+ val toggleHelpModal: Reusable[Callback] = Reusable.always(scope.modState(_.toggleHelpModal))
- val closeShareModal: Reusable[Callback] =
- Reusable.always(scope.modState(_.closeShareModal))
+ val closeShareModal: Reusable[Callback] = Reusable.always(scope.modState(_.closeShareModal))
val openShareModalOption: Option[SnippetId] ~=> Callback =
Reusable.fn(snippetId => scope.modState(_.openShareModal(snippetId)))
@@ -175,17 +136,13 @@ case class ScastieBackend(scastieId: UUID, serverUrl: Option[String], scope: Bac
val openShareModal: SnippetId ~=> Callback =
Reusable.fn(snippetId => scope.modState(_.openShareModal(Some(snippetId))))
- val openEmbeddedModal: Reusable[Callback] =
- Reusable.always(scope.modState(_.openEmbeddedModal))
+ val openEmbeddedModal: Reusable[Callback] = Reusable.always(scope.modState(_.openEmbeddedModal))
- val closeEmbeddedModal: Reusable[Callback] =
- Reusable.always(scope.modState(_.closeEmbeddedModal))
+ val closeEmbeddedModal: Reusable[Callback] = Reusable.always(scope.modState(_.closeEmbeddedModal))
- val forceDesktop: Reusable[Callback] =
- Reusable.always(scope.modState(_.forceDesktop))
+ val forceDesktop: Reusable[Callback] = Reusable.always(scope.modState(_.forceDesktop))
- val toggleWorksheetMode: Reusable[Callback] =
- Reusable.always(unlessEmbedded(_.toggleWorksheetMode))
+ val toggleWorksheetMode: Reusable[Callback] = Reusable.always(unlessEmbedded(_.toggleWorksheetMode))
private def unlessEmbedded(f: ScastieState => ScastieState): Callback = {
scope.props
@@ -207,11 +164,9 @@ case class ScastieBackend(scastieId: UUID, serverUrl: Option[String], scope: Bac
progress.isDone
}
- def onOpen(): Unit =
- direct.modState(_.logSystem("Connected. Waiting for sbt"))
+ def onOpen(): Unit = direct.modState(_.logSystem("Connected. Waiting for sbt"))
- def onError(error: String): Unit =
- direct.modState(_.logSystem(s"Error: $error"))
+ def onError(error: String): Unit = direct.modState(_.logSystem(s"Error: $error"))
def onClose(reason: Option[String]): Unit = {
val msg = reason.map(": " + _).getOrElse(".")
@@ -223,14 +178,12 @@ case class ScastieBackend(scastieId: UUID, serverUrl: Option[String], scope: Bac
)
}
- def onConnectionError(error: String): Callback =
- scope.modState(_.logSystem(s"Error: $error"))
+ def onConnectionError(error: String): Callback = scope.modState(_.logSystem(s"Error: $error"))
- def onConnected(stream: EventStream[SnippetProgress]): Callback =
- scope.modState(
- _.run(snippetId)
- .copy(progressStream = Some(stream))
- )
+ def onConnected(stream: EventStream[SnippetProgress]): Callback = scope.modState(
+ _.run(snippetId)
+ .copy(progressStream = Some(stream))
+ )
}
)
}
@@ -283,44 +236,41 @@ case class ScastieBackend(scastieId: UUID, serverUrl: Option[String], scope: Bac
)
}
- val run: Reusable[Callback] =
- Reusable.always(
- scope.state.flatMap(
- state =>
- Callback.future(
- restApiClient
- .run(state.inputs)
- .map(connectProgress)
- )
+ val run: Reusable[Callback] = Reusable.always(
+ scope.state.flatMap(state =>
+ Callback.future(
+ restApiClient
+ .run(state.inputs)
+ .map(connectProgress)
)
)
+ )
- val acceptPolicy: Reusable[Callback] =
- Reusable.always(
- Callback.future {
- restApiClient.acceptPrivacyPolicy().map { result =>
- scope.modState(_.setPrivacyPolicyPromptClosed(result))
- }
+ val acceptPolicy: Reusable[Callback] = Reusable.always(
+ Callback.future {
+ restApiClient.acceptPrivacyPolicy().map { result =>
+ scope.modState(_.setPrivacyPolicyPromptClosed(result))
}
- )
+ }
+ )
- val removeUserFromPolicyStatus: Reusable[Callback] =
- Reusable.always(
- Callback.future {
- restApiClient.removeUserFromPolicyStatus().map { result =>
- scope.modState(_.setPrivacyPolicyPromptClosed(result)).map(_ => {
+ val removeUserFromPolicyStatus: Reusable[Callback] = Reusable.always(
+ Callback.future {
+ restApiClient.removeUserFromPolicyStatus().map { result =>
+ scope
+ .modState(_.setPrivacyPolicyPromptClosed(result))
+ .map(_ => {
if (result) document.location.reload()
})
- }
}
- )
+ }
+ )
- val removeAllUserSnippets: Reusable[Callback] =
- Reusable.always(
- Callback.future {
- restApiClient.removeAllUserSnippets().map(Callback(_))
- }
- )
+ val removeAllUserSnippets: Reusable[Callback] = Reusable.always(
+ Callback.future {
+ restApiClient.removeAllUserSnippets().map(Callback(_))
+ }
+ )
val refusePrivacyPolicy: Reusable[Callback] = Reusable.always(
removeAllUserSnippets >> removeUserFromPolicyStatus
@@ -328,7 +278,7 @@ case class ScastieBackend(scastieId: UUID, serverUrl: Option[String], scope: Bac
private def saveCallback(sId: SnippetId): Callback = {
val setState = scope.modState(_.setCleanInputs.setSnippetId(sId).setLoadSnippet(false))
- val page = Page.fromSnippetId(sId)
+ val page = Page.fromSnippetId(sId)
val setUrl = scope.props.map {
_.router.foreach(r => org.scalajs.dom.window.history.pushState("", "", r.urlFor(page).value))
}
@@ -341,56 +291,52 @@ case class ScastieBackend(scastieId: UUID, serverUrl: Option[String], scope: Bac
}
}
- val saveBlocking: Reusable[CallbackTo[Option[SnippetId]]] =
- Reusable.always(
- scope.state.map(state => restApiClient.saveBlocking(state.inputs))
- )
+ val saveBlocking: Reusable[CallbackTo[Option[SnippetId]]] = Reusable.always(
+ scope.state.map(state => restApiClient.saveBlocking(state.inputs))
+ )
- val saveOrUpdate: Reusable[Callback] =
- Reusable.always(
- scope.props.flatMap { props =>
- scope.state
- .flatMap { state =>
- if (props.isEmbedded) {
- run
- } else {
- state.snippetId match {
- case Some(snippetId) =>
- if (snippetId.isOwnedBy(state.user)) {
- update0(snippetId)
- } else {
- fork0(snippetId)
- }
- case None => save0
- }
+ val saveOrUpdate: Reusable[Callback] = Reusable.always(
+ scope.props.flatMap { props =>
+ scope.state
+ .flatMap { state =>
+ if (props.isEmbedded) {
+ run
+ } else {
+ state.snippetId match {
+ case Some(snippetId) =>
+ if (snippetId.isOwnedBy(state.user)) {
+ update0(snippetId)
+ } else {
+ fork0(snippetId)
+ }
+ case None => save0
}
}
- }
- )
-
- private def fork0(snippetId: SnippetId): Callback =
- scope.state.flatMap { state =>
- Callback.future(
- restApiClient
- .fork(EditInputs(snippetId, state.inputs))
- .map {
- case Some(sId) => saveCallback(sId)
- case None => Callback(window.alert("Failed to fork"))
- }
- )
+ }
}
+ )
- private def update0(snippetId: SnippetId): Callback =
- scope.state.flatMap { state =>
- Callback.future(
- restApiClient
- .update(EditInputs(snippetId, state.inputs))
- .map {
- case Some(sId) => saveCallback(sId)
- case None => Callback(window.alert("Failed to update"))
- }
- )
- }
+ private def fork0(snippetId: SnippetId): Callback = scope.state.flatMap { state =>
+ Callback.future(
+ restApiClient
+ .fork(EditInputs(snippetId, state.inputs))
+ .map {
+ case Some(sId) => saveCallback(sId)
+ case None => Callback(window.alert("Failed to fork"))
+ }
+ )
+ }
+
+ private def update0(snippetId: SnippetId): Callback = scope.state.flatMap { state =>
+ Callback.future(
+ restApiClient
+ .update(EditInputs(snippetId, state.inputs))
+ .map {
+ case Some(sId) => saveCallback(sId)
+ case None => Callback(window.alert("Failed to update"))
+ }
+ )
+ }
def loadOldSnippet(id: Int): Callback = {
loadSnippetBase(
@@ -407,34 +353,31 @@ case class ScastieBackend(scastieId: UUID, serverUrl: Option[String], scope: Bac
}
private def loadSnippetBase(
- fetchSnippet: => Future[Option[FetchResult]],
- afterLoading: ScastieState => ScastieState = identity,
- snippetId: Option[SnippetId] = None
+ fetchSnippet: => Future[Option[FetchResult]],
+ afterLoading: ScastieState => ScastieState = identity,
+ snippetId: Option[SnippetId] = None
): Callback = {
scope.state.flatMap { state =>
if (state.loadSnippet) {
- val loadStateFromApi =
- Callback.future(
- fetchSnippet.map {
- case Some(FetchResult(inputs, progresses)) =>
- val isDone = progresses.exists(_.isDone)
- val connect =
- snippetId match {
- case Some(sid) if !isDone => connectProgress(sid)
- case _ => Callback(())
- }
- clearOutputs >> scope.modState { state =>
- afterLoading(
- state
- .setInputs(inputs)
- .setProgresses(progresses)
- .setCleanInputs
- )
- } >> connect
- case _ =>
- scope.modState(_.setCode(s"//snippet not found"))
- }
- )
+ val loadStateFromApi = Callback.future(
+ fetchSnippet.map {
+ case Some(FetchResult(inputs, progresses)) =>
+ val isDone = progresses.exists(_.isDone)
+ val connect = snippetId match {
+ case Some(sid) if !isDone => connectProgress(sid)
+ case _ => Callback(())
+ }
+ clearOutputs >> scope.modState { state =>
+ afterLoading(
+ state
+ .setInputs(inputs)
+ .setProgresses(progresses)
+ .setCleanInputs
+ )
+ } >> connect
+ case _ => scope.modState(_.setCode(s"//snippet not found"))
+ }
+ )
loadStateFromApi >>
setView(View.Editor) >>
@@ -447,16 +390,15 @@ case class ScastieBackend(scastieId: UUID, serverUrl: Option[String], scope: Bac
}
}
- def loadUser: Callback =
- Callback.future(
- restApiClient
- .fetchUser()
- .map(result => scope.modState(_.setUser(result)))
- ) >> Callback.future(
- restApiClient
- .getPrivacyPolicyStatus()
- .map(result => scope.modState(_.setPrivacyPolicyPromptClosed(result)))
- )
+ def loadUser: Callback = Callback.future(
+ restApiClient
+ .fetchUser()
+ .map(result => scope.modState(_.setUser(result)))
+ ) >> Callback.future(
+ restApiClient
+ .getPrivacyPolicyStatus()
+ .map(result => scope.modState(_.setPrivacyPolicyPromptClosed(result)))
+ )
val formatCode: Reusable[Callback] = Reusable.always {
scope.state.flatMap { state =>
@@ -464,31 +406,29 @@ case class ScastieBackend(scastieId: UUID, serverUrl: Option[String], scope: Bac
restApiClient
.format(FormatRequest(state.inputs.code, state.inputs.isWorksheetMode, state.inputs.target))
.map {
- case FormatResponse(Right(formattedCode)) =>
- scope.modState { s =>
+ case FormatResponse(Right(formattedCode)) => scope.modState { s =>
// avoid overriding user's code if he/she types while it's formatting
- if (s.inputs.code == state.inputs.code)
- s.clearOutputsPreserveConsole.setCode(formattedCode)
+ if (s.inputs.code == state.inputs.code) s.clearOutputsPreserveConsole.setCode(formattedCode)
else s
}
- case FormatResponse(Left(error)) =>
- scope.modState {
- _.setRuntimeError(Some(RuntimeError(message = "Formatting failed: " + error, line = None, fullStack = "")))
+ case FormatResponse(Left(error)) => scope.modState {
+ _.setRuntimeError(
+ Some(RuntimeError(message = "Formatting failed: " + error, line = None, fullStack = ""))
+ )
}
}
}
}
}
- val loadProfile: Reusable[Future[List[SnippetSummary]]] =
- Reusable.always(restApiClient.fetchUserSnippets())
+ val loadProfile: Reusable[Future[List[SnippetSummary]]] = Reusable.always(restApiClient.fetchUserSnippets())
- val deleteSnippet: SnippetId ~=> Future[Boolean] =
- Reusable.always(snippetId => restApiClient.delete(snippetId))
+ val deleteSnippet: SnippetId ~=> Future[Boolean] = Reusable.always(snippetId => restApiClient.delete(snippetId))
private def setHome = scope.props.flatMap(
_.router
.map(_.set(Home))
.getOrElse(Callback.empty)
)
+
}
diff --git a/client/src/main/scala/com.olegych.scastie.client/ScastieState.scala b/client/src/main/scala/com.olegych.scastie.client/ScastieState.scala
index b13872139..72fe4f8f5 100644
--- a/client/src/main/scala/com.olegych.scastie.client/ScastieState.scala
+++ b/client/src/main/scala/com.olegych.scastie.client/ScastieState.scala
@@ -1,8 +1,8 @@
package com.olegych.scastie.client
import com.olegych.scastie.api._
-import org.scalajs.dom.HTMLElement
import org.scalajs.dom.{Position => _}
+import org.scalajs.dom.HTMLElement
import play.api.libs.json._
sealed trait MetalsStatus {
@@ -30,17 +30,17 @@ case class NetworkError(msg: String) extends MetalsStatus {
}
object SnippetState {
- implicit val formatSnippetState: OFormat[SnippetState] =
- Json.format[SnippetState]
+ implicit val formatSnippetState: OFormat[SnippetState] = Json.format[SnippetState]
}
case class SnippetState(
- snippetId: Option[SnippetId],
- loadSnippet: Boolean,
- scalaJsContent: Option[String],
+ snippetId: Option[SnippetId],
+ loadSnippet: Boolean,
+ scalaJsContent: Option[String]
)
object ScastieState {
+
def default(isEmbedded: Boolean): ScastieState = {
ScastieState(
view = View.Editor,
@@ -59,7 +59,7 @@ object ScastieState {
snippetState = SnippetState(
snippetId = None,
loadSnippet = true,
- scalaJsContent = None,
+ scalaJsContent = None
),
user = None,
attachedDoms = Map(),
@@ -73,8 +73,7 @@ object ScastieState {
implicit val dontSerializeAttachedDoms: Format[Map[String, HTMLElement]] =
dontSerialize[Map[String, HTMLElement]](Map())
- implicit val dontSerializeStatusState: Format[StatusState] =
- dontSerialize[StatusState](StatusState.empty)
+ implicit val dontSerializeStatusState: Format[StatusState] = dontSerialize[StatusState](StatusState.empty)
implicit val dontSerializeEventStream: Format[EventStream[StatusProgress]] =
dontSerializeOption[EventStream[StatusProgress]]
@@ -82,92 +81,89 @@ object ScastieState {
implicit val dontSerializeProgressStream: Format[EventStream[SnippetProgress]] =
dontSerializeOption[EventStream[SnippetProgress]]
- implicit val dontSerializeMetalsStatus: Format[MetalsStatus] =
- dontSerialize[MetalsStatus](MetalsLoading)
+ implicit val dontSerializeMetalsStatus: Format[MetalsStatus] = dontSerialize[MetalsStatus](MetalsLoading)
- implicit val formatScastieState: OFormat[ScastieState] =
- Json.format[ScastieState]
+ implicit val formatScastieState: OFormat[ScastieState] = Json.format[ScastieState]
}
case class ScastieState(
- view: View,
- isRunning: Boolean,
- statusStream: Option[EventStream[StatusProgress]],
- progressStream: Option[EventStream[SnippetProgress]],
- modalState: ModalState,
- isDarkTheme: Boolean,
- isDesktopForced: Boolean,
- isPresentationMode: Boolean,
- showLineNumbers: Boolean,
- consoleState: ConsoleState,
- inputsHasChanged: Boolean,
- snippetState: SnippetState,
- user: Option[User],
- attachedDoms: Map[String, HTMLElement],
- inputs: Inputs,
- outputs: Outputs,
- status: StatusState,
- metalsStatus: MetalsStatus = MetalsLoading,
- isEmbedded: Boolean = false,
- transient: Boolean = false,
+ view: View,
+ isRunning: Boolean,
+ statusStream: Option[EventStream[StatusProgress]],
+ progressStream: Option[EventStream[SnippetProgress]],
+ modalState: ModalState,
+ isDarkTheme: Boolean,
+ isDesktopForced: Boolean,
+ isPresentationMode: Boolean,
+ showLineNumbers: Boolean,
+ consoleState: ConsoleState,
+ inputsHasChanged: Boolean,
+ snippetState: SnippetState,
+ user: Option[User],
+ attachedDoms: Map[String, HTMLElement],
+ inputs: Inputs,
+ outputs: Outputs,
+ status: StatusState,
+ metalsStatus: MetalsStatus = MetalsLoading,
+ isEmbedded: Boolean = false,
+ transient: Boolean = false
) {
def snippetId: Option[SnippetId] = snippetState.snippetId
- def loadSnippet: Boolean = snippetState.loadSnippet
+ def loadSnippet: Boolean = snippetState.loadSnippet
def copyAndSave(
- attachedDoms: Map[String, HTMLElement] = attachedDoms,
- view: View = view,
- isRunning: Boolean = isRunning,
- statusStream: Option[EventStream[StatusProgress]] = statusStream,
- progressStream: Option[EventStream[SnippetProgress]] = progressStream,
- modalState: ModalState = modalState,
- isDarkTheme: Boolean = isDarkTheme,
- isPresentationMode: Boolean = isPresentationMode,
- isDesktopForced: Boolean = isDesktopForced,
- showLineNumbers: Boolean = showLineNumbers,
- consoleState: ConsoleState = consoleState,
- inputsHasChanged: Boolean = inputsHasChanged,
- snippetId: Option[SnippetId] = snippetId,
- loadSnippet: Boolean = loadSnippet,
- scalaJsContent: Option[String] = snippetState.scalaJsContent,
- user: Option[User] = user,
- inputs: Inputs = inputs,
- outputs: Outputs = outputs,
- status: StatusState = status,
- metalsStatus: MetalsStatus = metalsStatus,
- transient: Boolean = transient,
+ attachedDoms: Map[String, HTMLElement] = attachedDoms,
+ view: View = view,
+ isRunning: Boolean = isRunning,
+ statusStream: Option[EventStream[StatusProgress]] = statusStream,
+ progressStream: Option[EventStream[SnippetProgress]] = progressStream,
+ modalState: ModalState = modalState,
+ isDarkTheme: Boolean = isDarkTheme,
+ isPresentationMode: Boolean = isPresentationMode,
+ isDesktopForced: Boolean = isDesktopForced,
+ showLineNumbers: Boolean = showLineNumbers,
+ consoleState: ConsoleState = consoleState,
+ inputsHasChanged: Boolean = inputsHasChanged,
+ snippetId: Option[SnippetId] = snippetId,
+ loadSnippet: Boolean = loadSnippet,
+ scalaJsContent: Option[String] = snippetState.scalaJsContent,
+ user: Option[User] = user,
+ inputs: Inputs = inputs,
+ outputs: Outputs = outputs,
+ status: StatusState = status,
+ metalsStatus: MetalsStatus = metalsStatus,
+ transient: Boolean = transient
): ScastieState = {
- val state0 =
- copy(
- view = view,
- isRunning = isRunning,
- statusStream = statusStream,
- progressStream = progressStream,
- modalState = modalState,
- isDarkTheme = isDarkTheme,
- isDesktopForced = isDesktopForced,
- isPresentationMode = isPresentationMode,
- showLineNumbers = showLineNumbers,
- consoleState = consoleState,
- inputsHasChanged = inputsHasChanged,
- snippetState = SnippetState(
- snippetId = snippetId,
- loadSnippet = loadSnippet,
- scalaJsContent = scalaJsContent,
- ),
- user = user,
- attachedDoms = attachedDoms,
- inputs = inputs.copy(
- isShowingInUserProfile = false,
- forked = None
- ),
- outputs = outputs,
- status = status,
- metalsStatus = metalsStatus,
- isEmbedded = isEmbedded,
- transient = transient,
- )
+ val state0 = copy(
+ view = view,
+ isRunning = isRunning,
+ statusStream = statusStream,
+ progressStream = progressStream,
+ modalState = modalState,
+ isDarkTheme = isDarkTheme,
+ isDesktopForced = isDesktopForced,
+ isPresentationMode = isPresentationMode,
+ showLineNumbers = showLineNumbers,
+ consoleState = consoleState,
+ inputsHasChanged = inputsHasChanged,
+ snippetState = SnippetState(
+ snippetId = snippetId,
+ loadSnippet = loadSnippet,
+ scalaJsContent = scalaJsContent
+ ),
+ user = user,
+ attachedDoms = attachedDoms,
+ inputs = inputs.copy(
+ isShowingInUserProfile = false,
+ forked = None
+ ),
+ outputs = outputs,
+ status = status,
+ metalsStatus = metalsStatus,
+ isEmbedded = isEmbedded,
+ transient = transient
+ )
if (!isEmbedded && !transient) {
LocalStorage.save(state0)
@@ -182,8 +178,7 @@ case class ScastieState(
def isBuildDefault: Boolean = inputs.isDefault
- def isClearable: Boolean =
- outputs.isClearable
+ def isClearable: Boolean = outputs.isClearable
def run(snippetId: SnippetId): ScastieState = {
clearOutputs.resetScalajs
@@ -197,82 +192,63 @@ case class ScastieState(
copyAndSave(isRunning = isRunning).autoOpen(openConsole)
}
- def toggleTheme: ScastieState =
- copyAndSave(isDarkTheme = !isDarkTheme)
+ def toggleTheme: ScastieState = copyAndSave(isDarkTheme = !isDarkTheme)
- def setTheme(dark: Boolean): ScastieState =
- copyAndSave(isDarkTheme = dark)
+ def setTheme(dark: Boolean): ScastieState = copyAndSave(isDarkTheme = dark)
- def setMetalsStatus(status: MetalsStatus): ScastieState =
- copyAndSave(metalsStatus = status)
+ def setMetalsStatus(status: MetalsStatus): ScastieState = copyAndSave(metalsStatus = status)
def toggleMetalsStatus: ScastieState =
copyAndSave(metalsStatus = if (metalsStatus != MetalsDisabled) MetalsDisabled else MetalsLoading)
- def toggleLineNumbers: ScastieState =
- copyAndSave(showLineNumbers = !showLineNumbers)
+ def toggleLineNumbers: ScastieState = copyAndSave(showLineNumbers = !showLineNumbers)
- def togglePresentationMode: ScastieState =
- copyAndSave(isPresentationMode = !isPresentationMode)
+ def togglePresentationMode: ScastieState = copyAndSave(isPresentationMode = !isPresentationMode)
- def toggleWorksheetMode: ScastieState =
- copyAndSave(
- inputs = inputs.copy(_isWorksheetMode = !inputs.isWorksheetMode),
- inputsHasChanged = true
- )
+ def toggleWorksheetMode: ScastieState = copyAndSave(
+ inputs = inputs.copy(_isWorksheetMode = !inputs.isWorksheetMode),
+ inputsHasChanged = true
+ )
- def toggleHelpModal: ScastieState =
- copyAndSave(
- modalState = modalState.copy(isHelpModalClosed = !modalState.isHelpModalClosed)
- )
+ def toggleHelpModal: ScastieState = copyAndSave(
+ modalState = modalState.copy(isHelpModalClosed = !modalState.isHelpModalClosed)
+ )
- def togglePrivacyPolicyModal: ScastieState =
- copyAndSave(
- modalState = modalState.copy(isPrivacyPolicyModalClosed = !modalState.isPrivacyPolicyModalClosed)
- )
+ def togglePrivacyPolicyModal: ScastieState = copyAndSave(
+ modalState = modalState.copy(isPrivacyPolicyModalClosed = !modalState.isPrivacyPolicyModalClosed)
+ )
- def setPrivacyPolicyPromptClosed(status: Boolean): ScastieState =
- copyAndSave(
- modalState = modalState.copy(isPrivacyPolicyPromptClosed = status)
- )
+ def setPrivacyPolicyPromptClosed(status: Boolean): ScastieState = copyAndSave(
+ modalState = modalState.copy(isPrivacyPolicyPromptClosed = status)
+ )
- def setLoginModalClosed(status: Boolean): ScastieState =
- copyAndSave(
- modalState = modalState.copy(isLoginModalClosed = status)
- )
+ def setLoginModalClosed(status: Boolean): ScastieState = copyAndSave(
+ modalState = modalState.copy(isLoginModalClosed = status)
+ )
- def openHelpModal: ScastieState =
- copyAndSave(modalState = modalState.copy(isHelpModalClosed = false))
+ def openHelpModal: ScastieState = copyAndSave(modalState = modalState.copy(isHelpModalClosed = false))
def openPrivacyPolicyModal: ScastieState =
copyAndSave(modalState = modalState.copy(isPrivacyPolicyModalClosed = false))
- def closeHelpModal: ScastieState =
- copyAndSave(modalState = modalState.copy(isHelpModalClosed = true))
+ def closeHelpModal: ScastieState = copyAndSave(modalState = modalState.copy(isHelpModalClosed = true))
- def openResetModal: ScastieState =
- copyAndSave(modalState = modalState.copy(isResetModalClosed = false))
+ def openResetModal: ScastieState = copyAndSave(modalState = modalState.copy(isResetModalClosed = false))
- def closeResetModal: ScastieState =
- copyAndSave(modalState = modalState.copy(isResetModalClosed = true))
+ def closeResetModal: ScastieState = copyAndSave(modalState = modalState.copy(isResetModalClosed = true))
- def openNewSnippetModal: ScastieState =
- copyAndSave(modalState = modalState.copy(isNewSnippetModalClosed = false))
+ def openNewSnippetModal: ScastieState = copyAndSave(modalState = modalState.copy(isNewSnippetModalClosed = false))
- def closeNewSnippetModal: ScastieState =
- copyAndSave(modalState = modalState.copy(isNewSnippetModalClosed = true))
+ def closeNewSnippetModal: ScastieState = copyAndSave(modalState = modalState.copy(isNewSnippetModalClosed = true))
def openShareModal(snippetId: Option[SnippetId]): ScastieState =
copyAndSave(modalState = modalState.copy(shareModalSnippetId = snippetId))
- def closeShareModal: ScastieState =
- copyAndSave(modalState = modalState.copy(shareModalSnippetId = None))
+ def closeShareModal: ScastieState = copyAndSave(modalState = modalState.copy(shareModalSnippetId = None))
- def openEmbeddedModal: ScastieState =
- copyAndSave(modalState = modalState.copy(isEmbeddedClosed = false))
+ def openEmbeddedModal: ScastieState = copyAndSave(modalState = modalState.copy(isEmbeddedClosed = false))
- def closeEmbeddedModal: ScastieState =
- copyAndSave(modalState = modalState.copy(isEmbeddedClosed = true))
+ def closeEmbeddedModal: ScastieState = copyAndSave(modalState = modalState.copy(isEmbeddedClosed = true))
def forceDesktop: ScastieState = copyAndSave(isDesktopForced = true)
@@ -305,16 +281,14 @@ case class ScastieState(
def toggleConsole: ScastieState = {
copyAndSave(
consoleState =
- if (consoleState.consoleIsOpen)
- consoleState.copy(
- consoleIsOpen = false,
- userOpenedConsole = false
- )
- else
- consoleState.copy(
- consoleIsOpen = true,
- userOpenedConsole = true
- )
+ if (consoleState.consoleIsOpen) consoleState.copy(
+ consoleIsOpen = false,
+ userOpenedConsole = false
+ )
+ else consoleState.copy(
+ consoleIsOpen = true,
+ userOpenedConsole = true
+ )
)
}
@@ -322,11 +296,9 @@ case class ScastieState(
copyAndSave(consoleState = consoleState.copy(consoleHasUserOutput = true))
}
- def setLoadSnippet(value: Boolean): ScastieState =
- copy(snippetState = snippetState.copy(loadSnippet = value))
+ def setLoadSnippet(value: Boolean): ScastieState = copy(snippetState = snippetState.copy(loadSnippet = value))
- def setUser(user: Option[User]): ScastieState =
- copyAndSave(user = user)
+ def setUser(user: Option[User]): ScastieState = copyAndSave(user = user)
def setCode(code: String): ScastieState = {
if (inputs.code != code) {
@@ -339,51 +311,43 @@ case class ScastieState(
}
}
- def setInputs(inputs: Inputs): ScastieState =
- copyAndSave(
- inputs = inputs
- )
+ def setInputs(inputs: Inputs): ScastieState = copyAndSave(
+ inputs = inputs
+ )
- def setSbtConfigExtra(config: String): ScastieState =
- copyAndSave(
- inputs = inputs.copy(sbtConfigExtra = config),
- inputsHasChanged = true
- )
+ def setSbtConfigExtra(config: String): ScastieState = copyAndSave(
+ inputs = inputs.copy(sbtConfigExtra = config),
+ inputsHasChanged = true
+ )
- def setChangedInputs: ScastieState =
- copyAndSave(inputsHasChanged = true)
+ def setChangedInputs: ScastieState = copyAndSave(inputsHasChanged = true)
- def setCleanInputs: ScastieState =
- copyAndSave(inputsHasChanged = false)
+ def setCleanInputs: ScastieState = copyAndSave(inputsHasChanged = false)
- def setView(newView: View): ScastieState =
- copyAndSave(view = newView)
+ def setView(newView: View): ScastieState = copyAndSave(view = newView)
- def setTarget(target: ScalaTarget): ScastieState =
- copyAndSave(
- inputs = inputs.modifyConfig(_.copy(target = target)),
- inputsHasChanged = true
- )
+ def setTarget(target: ScalaTarget): ScastieState = copyAndSave(
+ inputs = inputs.modifyConfig(_.copy(target = target)),
+ inputsHasChanged = true
+ )
- def clearDependencies: ScastieState =
- copyAndSave(
- inputs = inputs.clearDependencies,
- inputsHasChanged = true
- )
+ def clearDependencies: ScastieState = copyAndSave(
+ inputs = inputs.clearDependencies,
+ inputsHasChanged = true
+ )
def addScalaDependency(scalaDependency: ScalaDependency, project: Project): ScastieState = {
val newInputs = inputs.addScalaDependency(scalaDependency, project)
copyAndSave(
inputs = newInputs,
- inputsHasChanged = newInputs != inputs,
+ inputsHasChanged = newInputs != inputs
)
}
- def removeScalaDependency(scalaDependency: ScalaDependency): ScastieState =
- copyAndSave(
- inputs = inputs.removeScalaDependency(scalaDependency),
- inputsHasChanged = true
- )
+ def removeScalaDependency(scalaDependency: ScalaDependency): ScastieState = copyAndSave(
+ inputs = inputs.removeScalaDependency(scalaDependency),
+ inputsHasChanged = true
+ )
def updateDependencyVersion(scalaDependency: ScalaDependency, version: String): ScastieState = {
copyAndSave(
@@ -407,24 +371,21 @@ case class ScastieState(
def clearOutputsPreserveConsole: ScastieState = {
copyAndSave(
- outputs = Outputs.default.copy(consoleOutputs = outputs.consoleOutputs),
+ outputs = Outputs.default.copy(consoleOutputs = outputs.consoleOutputs)
)
}
- def closeModals: ScastieState =
- copyAndSave(modalState = ModalState.allClosed)
+ def closeModals: ScastieState = copyAndSave(modalState = ModalState.allClosed)
def setRuntimeError(runtimeError: Option[RuntimeError]): ScastieState =
if (runtimeError.isEmpty) this
else copyAndSave(outputs = outputs.copy(runtimeError = runtimeError))
- def setSbtError(err: Boolean): ScastieState =
- copyAndSave(outputs = outputs.copy(sbtError = err))
+ def setSbtError(err: Boolean): ScastieState = copyAndSave(outputs = outputs.copy(sbtError = err))
def logOutput(line: Option[ProcessOutput], wrap: ProcessOutput => ConsoleOutput): ScastieState = {
line match {
- case Some(l) =>
- copyAndSave(
+ case Some(l) => copyAndSave(
outputs = outputs.copy(
consoleOutputs = outputs.consoleOutputs ++ Vector(wrap(l))
)
@@ -460,10 +421,8 @@ case class ScastieState(
def addStatus(statusUpdate: StatusProgress): ScastieState = {
statusUpdate match {
- case StatusProgress.KeepAlive =>
- this
- case StatusProgress.Sbt(sbtRunners) =>
- copyAndSave(status = status.copy(sbtRunners = Some(sbtRunners)))
+ case StatusProgress.KeepAlive => this
+ case StatusProgress.Sbt(sbtRunners) => copyAndSave(status = status.copy(sbtRunners = Some(sbtRunners)))
}
}
@@ -472,8 +431,8 @@ case class ScastieState(
}
def setProgresses(progresses: List[SnippetProgress]): ScastieState = coalesceUpdates { self =>
- progresses.foldLeft(self) {
- case (state, progress) => state.addProgress(progress)
+ progresses.foldLeft(self) { case (state, progress) =>
+ state.addProgress(progress)
}
}
@@ -506,20 +465,18 @@ case class ScastieState(
val useWorksheetModeTip =
if (compilationInfos.exists(ci => topDef(ci)))
- if (inputs.target.hasWorksheetMode)
- Set(
- info(
- """|It seems you're writing code without an enclosing class/object.
- |Switch to Worksheet mode if you want to use scastie more like a REPL.""".stripMargin
- )
+ if (inputs.target.hasWorksheetMode) Set(
+ info(
+ """|It seems you're writing code without an enclosing class/object.
+ |Switch to Worksheet mode if you want to use scastie more like a REPL.""".stripMargin
)
- else
- Set(
- info(
- """|It seems you're writing code without an enclosing class/object.
- |This configuration does not support Worksheet mode.""".stripMargin
- )
+ )
+ else Set(
+ info(
+ """|It seems you're writing code without an enclosing class/object.
+ |This configuration does not support Worksheet mode.""".stripMargin
)
+ )
else Set()
copyAndSave(
diff --git a/client/src/main/scala/com.olegych.scastie.client/StatusState.scala b/client/src/main/scala/com.olegych.scastie.client/StatusState.scala
index dda0e8e17..282a15be4 100644
--- a/client/src/main/scala/com.olegych.scastie.client/StatusState.scala
+++ b/client/src/main/scala/com.olegych.scastie.client/StatusState.scala
@@ -8,5 +8,5 @@ object StatusState {
final case class StatusState(sbtRunners: Option[Vector[SbtRunnerState]]) {
def sbtRunnerCount: Option[Int] = sbtRunners.map(_.size)
- def isSbtOk: Boolean = sbtRunners.exists(_.nonEmpty)
+ def isSbtOk: Boolean = sbtRunners.exists(_.nonEmpty)
}
diff --git a/client/src/main/scala/com.olegych.scastie.client/Views.scala b/client/src/main/scala/com.olegych.scastie.client/Views.scala
index 9127fbd1e..af909448f 100644
--- a/client/src/main/scala/com.olegych.scastie.client/Views.scala
+++ b/client/src/main/scala/com.olegych.scastie.client/Views.scala
@@ -3,24 +3,25 @@ package com.olegych.scastie.client
import play.api.libs.json._
sealed trait View
+
object View {
- case object Editor extends View
+ case object Editor extends View
case object BuildSettings extends View
- case object CodeSnippets extends View
- case object Status extends View
+ case object CodeSnippets extends View
+ case object Status extends View
implicit object ViewFormat extends Format[View] {
+
def writes(view: View): JsValue = {
JsString(view.toString)
}
- private val values: Map[String, View] =
- List[View](
- Editor,
- BuildSettings,
- CodeSnippets,
- Status
- ).map(v => (v.toString, v)).toMap
+ private val values: Map[String, View] = List[View](
+ Editor,
+ BuildSettings,
+ CodeSnippets,
+ Status
+ ).map(v => (v.toString, v)).toMap
def reads(json: JsValue): JsResult[View] = {
json match {
@@ -33,5 +34,7 @@ object View {
case _ => JsError(Seq())
}
}
+
}
+
}
diff --git a/client/src/main/scala/com.olegych.scastie.client/components/BuildSettings.scala b/client/src/main/scala/com.olegych.scastie.client/components/BuildSettings.scala
index f43c6572a..17b104eba 100644
--- a/client/src/main/scala/com.olegych.scastie.client/components/BuildSettings.scala
+++ b/client/src/main/scala/com.olegych.scastie.client/components/BuildSettings.scala
@@ -3,27 +3,26 @@ package com.olegych.scastie.client.components
import com.olegych.scastie.api._
import com.olegych.scastie.client.components.editor.SimpleEditor
import japgolly.scalajs.react._
-
import vdom.all._
final case class BuildSettings(
- visible: Boolean,
- librariesFrom: Map[ScalaDependency, Project],
- isDarkTheme: Boolean,
- isBuildDefault: Boolean,
- isResetModalClosed: Boolean,
- scalaTarget: ScalaTarget,
- sbtConfigExtra: String,
- sbtConfig: String,
- sbtPluginsConfig: String,
- setTarget: ScalaTarget ~=> Callback,
- closeResetModal: Reusable[Callback],
- resetBuild: Reusable[Callback],
- openResetModal: Reusable[Callback],
- sbtConfigChange: String ~=> Callback,
- removeScalaDependency: ScalaDependency ~=> Callback,
- updateDependencyVersion: (ScalaDependency, String) ~=> Callback,
- addScalaDependency: (ScalaDependency, Project) ~=> Callback
+ visible: Boolean,
+ librariesFrom: Map[ScalaDependency, Project],
+ isDarkTheme: Boolean,
+ isBuildDefault: Boolean,
+ isResetModalClosed: Boolean,
+ scalaTarget: ScalaTarget,
+ sbtConfigExtra: String,
+ sbtConfig: String,
+ sbtPluginsConfig: String,
+ setTarget: ScalaTarget ~=> Callback,
+ closeResetModal: Reusable[Callback],
+ resetBuild: Reusable[Callback],
+ openResetModal: Reusable[Callback],
+ sbtConfigChange: String ~=> Callback,
+ removeScalaDependency: ScalaDependency ~=> Callback,
+ updateDependencyVersion: (ScalaDependency, String) ~=> Callback,
+ addScalaDependency: (ScalaDependency, Project) ~=> Callback
) {
@inline def render: VdomElement = BuildSettings.component(this)
@@ -31,8 +30,7 @@ final case class BuildSettings(
object BuildSettings {
- implicit val reusability: Reusability[BuildSettings] =
- Reusability.derive[BuildSettings]
+ implicit val reusability: Reusability[BuildSettings] = Reusability.derive[BuildSettings]
private def render(props: BuildSettings): VdomElement = {
@@ -51,7 +49,7 @@ object BuildSettings {
title := "Reset your configuration",
onClick --> props.openResetModal,
role := "button",
- cls := "btn",
+ cls := "btn",
if (props.isBuildDefault) visibility.collapse else visibility.visible
)(
"Reset"
@@ -69,7 +67,7 @@ object BuildSettings {
div(cls := "build-settings-container")(
resetButton,
h2(
- span("Target"),
+ span("Target")
),
TargetSelector(props.scalaTarget, props.setTarget).render,
h2(
@@ -116,10 +114,10 @@ object BuildSettings {
)
}
- private val component =
- ScalaComponent
- .builder[BuildSettings]("BuildSettings")
- .render_P(render)
- .configure(Reusability.shouldComponentUpdate)
- .build
+ private val component = ScalaComponent
+ .builder[BuildSettings]("BuildSettings")
+ .render_P(render)
+ .configure(Reusability.shouldComponentUpdate)
+ .build
+
}
diff --git a/client/src/main/scala/com.olegych.scastie.client/components/ClearButton.scala b/client/src/main/scala/com.olegych.scastie.client/components/ClearButton.scala
index 094ab50c4..84854550c 100644
--- a/client/src/main/scala/com.olegych.scastie.client/components/ClearButton.scala
+++ b/client/src/main/scala/com.olegych.scastie.client/components/ClearButton.scala
@@ -3,7 +3,6 @@ package components
import com.olegych.scastie.client.components.editor.EditorKeymaps
import japgolly.scalajs.react._
-
import vdom.all._
final case class ClearButton(clear: Reusable[Callback]) {
@@ -12,14 +11,13 @@ final case class ClearButton(clear: Reusable[Callback]) {
object ClearButton {
- implicit val reusability: Reusability[ClearButton] =
- Reusability.derive[ClearButton]
+ implicit val reusability: Reusability[ClearButton] = Reusability.derive[ClearButton]
private def render(props: ClearButton): VdomElement = {
li(
title := s"Clear Messages (${EditorKeymaps.clear.getName})",
- role := "button",
- cls := "btn",
+ role := "button",
+ cls := "btn",
onClick --> props.clear
)(
div(
@@ -29,10 +27,10 @@ object ClearButton {
)
}
- private val component =
- ScalaComponent
- .builder[ClearButton]("ClearButton")
- .render_P(render)
- .configure(Reusability.shouldComponentUpdate)
- .build
+ private val component = ScalaComponent
+ .builder[ClearButton]("ClearButton")
+ .render_P(render)
+ .configure(Reusability.shouldComponentUpdate)
+ .build
+
}
diff --git a/client/src/main/scala/com.olegych.scastie.client/components/CodeSnippets.scala b/client/src/main/scala/com.olegych.scastie.client/components/CodeSnippets.scala
index 02e5c918d..630e7d29c 100644
--- a/client/src/main/scala/com.olegych.scastie.client/components/CodeSnippets.scala
+++ b/client/src/main/scala/com.olegych.scastie.client/components/CodeSnippets.scala
@@ -1,74 +1,68 @@
package com.olegych.scastie.client.components
+import scala.concurrent.Future
+
import com.olegych.scastie.api._
import com.olegych.scastie.client.Page
import com.olegych.scastie.client.View
+import extra.router._
import japgolly.scalajs.react._
import japgolly.scalajs.react.component.builder.Lifecycle.RenderScope
-
-import scala.concurrent.Future
-
-import vdom.all._
-import extra.router._
import scalajs.concurrent.JSExecutionContext.Implicits.queue
+import vdom.all._
final case class CodeSnippets(
- view: View,
- user: User,
- router: RouterCtl[Page],
- isDarkTheme: Boolean,
- isShareModalClosed: SnippetId ~=> Boolean,
- closeShareModal: Reusable[Callback],
- openShareModal: SnippetId ~=> Callback,
- loadProfile: Reusable[Future[List[SnippetSummary]]],
- deleteSnippet: SnippetId ~=> Future[Boolean]
+ view: View,
+ user: User,
+ router: RouterCtl[Page],
+ isDarkTheme: Boolean,
+ isShareModalClosed: SnippetId ~=> Boolean,
+ closeShareModal: Reusable[Callback],
+ openShareModal: SnippetId ~=> Callback,
+ loadProfile: Reusable[Future[List[SnippetSummary]]],
+ deleteSnippet: SnippetId ~=> Future[Boolean]
) {
@inline def render: VdomElement = CodeSnippets.component(this)
}
object CodeSnippets {
- implicit val reusability: Reusability[CodeSnippets] =
- Reusability.derive[CodeSnippets]
+ implicit val reusability: Reusability[CodeSnippets] = Reusability.derive[CodeSnippets]
private[CodeSnippets] class CodeSnippetsBackend(
- scope: BackendScope[CodeSnippets, List[SnippetSummary]]
+ scope: BackendScope[CodeSnippets, List[SnippetSummary]]
) {
def loadProfile0(): Callback = {
- scope.props.flatMap(
- props =>
- Callback.future(
- props.loadProfile.map(
- _.map(summaries => scope.modState(_ => summaries))
- )
+ scope.props.flatMap(props =>
+ Callback.future(
+ props.loadProfile.map(
+ _.map(summaries => scope.modState(_ => summaries))
+ )
)
)
}
def deleteSnippet0(summary: SnippetSummary): Callback = {
- scope.props.flatMap(
- props =>
- Callback.future(
- props
- .deleteSnippet(summary.snippetId)
- .map(
- deleted => scope.modState(_.filterNot(_ == summary)).when_(deleted)
- )
+ scope.props.flatMap(props =>
+ Callback.future(
+ props
+ .deleteSnippet(summary.snippetId)
+ .map(deleted => scope.modState(_.filterNot(_ == summary)).when_(deleted))
)
)
}
+
}
private def renderSnippet(backend: CodeSnippetsBackend, props: CodeSnippets)(
- summary: SnippetSummary
+ summary: SnippetSummary
): VdomElement = {
- val page = Page.fromSnippetId(summary.snippetId)
+ val page = Page.fromSnippetId(summary.snippetId)
val update = summary.snippetId.user.map(_.update.toString).getOrElse("")
- val snippetUrl =
- props.router.urlFor(Page.fromSnippetId(summary.snippetId)).value
+ val snippetUrl = props.router.urlFor(Page.fromSnippetId(summary.snippetId)).value
div(cls := "snippet")(
CopyModal(
@@ -82,15 +76,15 @@ object CodeSnippets {
).render,
div(cls := "header", "/" + summary.snippetId.base64UUID)(
span(" - "),
- div(cls := "clear-mobile"),
+ div(cls := "clear-mobile"),
span(cls := "update", "Update: " + update),
div(cls := "actions")(
li(onClick --> props.openShareModal(summary.snippetId), cls := "btn", title := "Share", role := "button")(
i(cls := "fa fa-share-alt")
),
li(
- cls := "btn",
- role := "button",
+ cls := "btn",
+ role := "button",
title := "Delete",
onClick --> backend.deleteSnippet0(summary)
)(
@@ -108,21 +102,20 @@ object CodeSnippets {
}
private def render(
- scope: RenderScope[
- CodeSnippets,
- List[SnippetSummary],
- CodeSnippetsBackend
- ],
- props: CodeSnippets,
- summaries: List[SnippetSummary]
+ scope: RenderScope[
+ CodeSnippets,
+ List[SnippetSummary],
+ CodeSnippetsBackend
+ ],
+ props: CodeSnippets,
+ summaries: List[SnippetSummary]
): VdomElement = {
- val userAvatar =
- div(cls := "avatar")(
- img(src := props.user.avatar_url + "&s=70", alt := "Your Github Avatar", cls := "image-button avatar")
- )
+ val userAvatar = div(cls := "avatar")(
+ img(src := props.user.avatar_url + "&s=70", alt := "Your Github Avatar", cls := "image-button avatar")
+ )
- val userName = props.user.name.getOrElse("")
+ val userName = props.user.name.getOrElse("")
val userLogin = props.user.login
val noSummaries =
@@ -133,11 +126,10 @@ object CodeSnippets {
xs.groupBy(_.snippetId.base64UUID)
.toList
- .flatMap {
- case (_, snippets) =>
- List(
- snippets.sortBy(_.snippetId.user.map(_.update).getOrElse(0)).last
- )
+ .flatMap { case (_, snippets) =>
+ List(
+ snippets.sortBy(_.snippetId.user.map(_.update).getOrElse(0)).last
+ )
}
.sortBy(_.time)
.reverse
@@ -154,10 +146,9 @@ object CodeSnippets {
div(cls := "snippets")(
noSummaries,
sortSnippets(summaries)
- .map(
- summary =>
- div(cls := "group", key := summary.snippetId.base64UUID)(
- renderSnippet(scope.backend, props)(summary)
+ .map(summary =>
+ div(cls := "group", key := summary.snippetId.base64UUID)(
+ renderSnippet(scope.backend, props)(summary)
)
)
.toTagMod
@@ -165,23 +156,21 @@ object CodeSnippets {
)
}
- private val component =
- ScalaComponent
- .builder[CodeSnippets]("CodeSnippets")
- .initialState(List.empty[SnippetSummary])
- .backend(new CodeSnippetsBackend(_))
- .renderPS(render)
- .componentWillReceiveProps { delta =>
- val viewChangedToCodeSnippet =
- delta.currentProps.view != View.CodeSnippets &&
- delta.nextProps.view == View.CodeSnippets
-
- val loadProfile: Callback =
- delta.backend.loadProfile0()
-
- loadProfile.when_(viewChangedToCodeSnippet)
- }
- .componentWillMount(_.backend.loadProfile0())
- .configure(Reusability.shouldComponentUpdate)
- .build
+ private val component = ScalaComponent
+ .builder[CodeSnippets]("CodeSnippets")
+ .initialState(List.empty[SnippetSummary])
+ .backend(new CodeSnippetsBackend(_))
+ .renderPS(render)
+ .componentWillReceiveProps { delta =>
+ val viewChangedToCodeSnippet = delta.currentProps.view != View.CodeSnippets &&
+ delta.nextProps.view == View.CodeSnippets
+
+ val loadProfile: Callback = delta.backend.loadProfile0()
+
+ loadProfile.when_(viewChangedToCodeSnippet)
+ }
+ .componentWillMount(_.backend.loadProfile0())
+ .configure(Reusability.shouldComponentUpdate)
+ .build
+
}
diff --git a/client/src/main/scala/com.olegych.scastie.client/components/Console.scala b/client/src/main/scala/com.olegych.scastie.client/components/Console.scala
index d49c4a374..490d648c6 100644
--- a/client/src/main/scala/com.olegych.scastie.client/components/Console.scala
+++ b/client/src/main/scala/com.olegych.scastie.client/components/Console.scala
@@ -6,24 +6,24 @@ import com.olegych.scastie.client.HTMLFormatter
import com.olegych.scastie.client.View
import japgolly.scalajs.react._
import org.scalajs.dom.raw.HTMLDivElement
-
import vdom.all._
-final case class Console(isOpen: Boolean,
- isRunning: Boolean,
- isEmbedded: Boolean,
- consoleOutputs: Vector[ConsoleOutput],
- run: Reusable[Callback],
- setView: View ~=> Callback,
- close: Reusable[Callback],
- open: Reusable[Callback]) {
+final case class Console(
+ isOpen: Boolean,
+ isRunning: Boolean,
+ isEmbedded: Boolean,
+ consoleOutputs: Vector[ConsoleOutput],
+ run: Reusable[Callback],
+ setView: View ~=> Callback,
+ close: Reusable[Callback],
+ open: Reusable[Callback]
+) {
@inline def render: VdomElement = Console.component(this)
}
object Console {
- implicit val reusability: Reusability[Console] =
- Reusability.derive[Console]
+ implicit val reusability: Reusability[Console] = Reusability.derive[Console]
private val consoleElement = Ref[HTMLDivElement]
@@ -33,8 +33,7 @@ object Console {
else (display.none, display.flex)
val consoleCss =
- if (props.isOpen)
- TagMod(cls := "console-open")
+ if (props.isOpen) TagMod(cls := "console-open")
else EmptyVdom
val (users, systems) = props.consoleOutputs.partition {
@@ -59,7 +58,7 @@ object Console {
isStatusOk = true,
save = props.run,
setView = props.setView,
- embedded = true,
+ embedded = true
).render.when(props.isEmbedded),
div(cls := "console-label")(
i(cls := "fa fa-terminal"),
@@ -68,7 +67,7 @@ object Console {
)
),
div.withRef(consoleElement)(
- cls := "output-console",
+ cls := "output-console",
dangerouslySetInnerHtml := renderConsoleOutputs
)
),
@@ -78,7 +77,7 @@ object Console {
isStatusOk = true,
save = props.run,
setView = props.setView,
- embedded = true,
+ embedded = true
).render.when(props.isEmbedded),
displaySwitcher,
div(cls := "console-label")(
@@ -90,17 +89,16 @@ object Console {
)
}
- private val component =
- ScalaComponent
- .builder[Console]("Console")
- .initialState(ConsoleState.default)
- .render_P(render)
- .componentDidUpdate(
- scope =>
- Callback {
- consoleElement.unsafeGet().scrollTop = consoleElement.unsafeGet().scrollHeight.toDouble
- }.when_(scope.prevProps.isRunning)
- )
- .configure(Reusability.shouldComponentUpdate)
- .build
+ private val component = ScalaComponent
+ .builder[Console]("Console")
+ .initialState(ConsoleState.default)
+ .render_P(render)
+ .componentDidUpdate(scope =>
+ Callback {
+ consoleElement.unsafeGet().scrollTop = consoleElement.unsafeGet().scrollHeight.toDouble
+ }.when_(scope.prevProps.isRunning)
+ )
+ .configure(Reusability.shouldComponentUpdate)
+ .build
+
}
diff --git a/client/src/main/scala/com.olegych.scastie.client/components/CopyModal.scala b/client/src/main/scala/com.olegych.scastie.client/components/CopyModal.scala
index c30244677..8a3702e4c 100644
--- a/client/src/main/scala/com.olegych.scastie.client/components/CopyModal.scala
+++ b/client/src/main/scala/com.olegych.scastie.client/components/CopyModal.scala
@@ -55,7 +55,7 @@ object CopyModal {
props.content
),
div(onClick --> copyLink, title := "Copy to Clipboard", cls := "snippet-clip clipboard-copy")(
- i(cls := "fa fa-clipboard")
+ i(cls := "fa fa-clipboard")
)
)
)
diff --git a/client/src/main/scala/com.olegych.scastie.client/components/DesktopButton.scala b/client/src/main/scala/com.olegych.scastie.client/components/DesktopButton.scala
index 56d91a99a..b3e0117ac 100644
--- a/client/src/main/scala/com.olegych.scastie.client/components/DesktopButton.scala
+++ b/client/src/main/scala/com.olegych.scastie.client/components/DesktopButton.scala
@@ -3,7 +3,6 @@ package client
package components
import japgolly.scalajs.react._
-
import vdom.all._
final case class DesktopButton(forceDesktop: Reusable[Callback]) {
@@ -11,8 +10,7 @@ final case class DesktopButton(forceDesktop: Reusable[Callback]) {
}
object DesktopButton {
- implicit val reusability: Reusability[DesktopButton] =
- Reusability.derive[DesktopButton]
+ implicit val reusability: Reusability[DesktopButton] = Reusability.derive[DesktopButton]
private def render(props: DesktopButton): VdomElement = {
li(title := "Go to desktop", cls := "btn", onClick --> props.forceDesktop)(
@@ -21,10 +19,10 @@ object DesktopButton {
)
}
- private val component =
- ScalaComponent
- .builder[DesktopButton]("DesktopButton")
- .render_P(render)
- .configure(Reusability.shouldComponentUpdate)
- .build
+ private val component = ScalaComponent
+ .builder[DesktopButton]("DesktopButton")
+ .render_P(render)
+ .configure(Reusability.shouldComponentUpdate)
+ .build
+
}
diff --git a/client/src/main/scala/com.olegych.scastie.client/components/DownloadButton.scala b/client/src/main/scala/com.olegych.scastie.client/components/DownloadButton.scala
index 36b57ebf6..405f59df9 100644
--- a/client/src/main/scala/com.olegych.scastie.client/components/DownloadButton.scala
+++ b/client/src/main/scala/com.olegych.scastie.client/components/DownloadButton.scala
@@ -3,7 +3,6 @@ package components
import com.olegych.scastie.api.SnippetId
import japgolly.scalajs.react._
-
import vdom.all._
final case class DownloadButton(snippetId: SnippetId) {
@@ -11,25 +10,30 @@ final case class DownloadButton(snippetId: SnippetId) {
}
object DownloadButton {
- implicit val reusability: Reusability[DownloadButton] =
- Reusability.derive[DownloadButton]
+ implicit val reusability: Reusability[DownloadButton] = Reusability.derive[DownloadButton]
def render(props: DownloadButton): VdomElement = {
- val url = props.snippetId.url
+ val url = props.snippetId.url
val fullUrl = s"/api/download/$url"
li(
- a(href := fullUrl, download := url.replaceAll("/", "-") + ".zip", title := s"Download", role := "button", cls := "btn")(
+ a(
+ href := fullUrl,
+ download := url.replaceAll("/", "-") + ".zip",
+ title := s"Download",
+ role := "button",
+ cls := "btn"
+ )(
i(cls := "fa fa-download"),
span("Download")
)
)
}
- private val component =
- ScalaComponent
- .builder[DownloadButton]("DownloadButton")
- .render_P(render)
- .configure(Reusability.shouldComponentUpdate)
- .build
+ private val component = ScalaComponent
+ .builder[DownloadButton]("DownloadButton")
+ .render_P(render)
+ .configure(Reusability.shouldComponentUpdate)
+ .build
+
}
diff --git a/client/src/main/scala/com.olegych.scastie.client/components/EditorTopBar.scala b/client/src/main/scala/com.olegych.scastie.client/components/EditorTopBar.scala
index 7fea28cba..6da746ad7 100644
--- a/client/src/main/scala/com.olegych.scastie.client/components/EditorTopBar.scala
+++ b/client/src/main/scala/com.olegych.scastie.client/components/EditorTopBar.scala
@@ -2,40 +2,43 @@ package com.olegych.scastie
package client
package components
-import api.{SnippetId, User, ScalaTarget}
-
-import japgolly.scalajs.react._, vdom.all._, extra.router._, extra._
-
-final case class EditorTopBar(clear: Reusable[Callback],
- closeNewSnippetModal: Reusable[Callback],
- closeEmbeddedModal: Reusable[Callback],
- openEmbeddedModal: Reusable[Callback],
- formatCode: Reusable[Callback],
- newSnippet: Reusable[Callback],
- openNewSnippetModal: Reusable[Callback],
- save: Reusable[Callback],
- toggleWorksheetMode: Reusable[Callback],
- router: Option[RouterCtl[Page]],
- inputsHasChanged: Boolean,
- isDarkTheme: Boolean,
- isNewSnippetModalClosed: Boolean,
- isEmbeddedModalClosed: Boolean,
- isRunning: Boolean,
- isStatusOk: Boolean,
- snippetId: Option[SnippetId],
- user: Option[User],
- view: StateSnapshot[View],
- isWorksheetMode: Boolean,
- metalsStatus: MetalsStatus,
- toggleMetalsStatus: Reusable[Callback],
- scalaTarget: ScalaTarget) {
+import api.{ScalaTarget, SnippetId, User}
+import extra._
+import extra.router._
+import japgolly.scalajs.react._
+import vdom.all._
+
+final case class EditorTopBar(
+ clear: Reusable[Callback],
+ closeNewSnippetModal: Reusable[Callback],
+ closeEmbeddedModal: Reusable[Callback],
+ openEmbeddedModal: Reusable[Callback],
+ formatCode: Reusable[Callback],
+ newSnippet: Reusable[Callback],
+ openNewSnippetModal: Reusable[Callback],
+ save: Reusable[Callback],
+ toggleWorksheetMode: Reusable[Callback],
+ router: Option[RouterCtl[Page]],
+ inputsHasChanged: Boolean,
+ isDarkTheme: Boolean,
+ isNewSnippetModalClosed: Boolean,
+ isEmbeddedModalClosed: Boolean,
+ isRunning: Boolean,
+ isStatusOk: Boolean,
+ snippetId: Option[SnippetId],
+ user: Option[User],
+ view: StateSnapshot[View],
+ isWorksheetMode: Boolean,
+ metalsStatus: MetalsStatus,
+ toggleMetalsStatus: Reusable[Callback],
+ scalaTarget: ScalaTarget
+) {
@inline def render: VdomElement = EditorTopBar.component(this)
}
object EditorTopBar {
- implicit val reusability: Reusability[EditorTopBar] =
- Reusability.derive[EditorTopBar]
+ implicit val reusability: Reusability[EditorTopBar] = Reusability.derive[EditorTopBar]
private def render(props: EditorTopBar): VdomElement = {
def isDisabled = (cls := "disabled").when(props.view.value != View.Editor)
@@ -45,7 +48,7 @@ object EditorTopBar {
isStatusOk = props.isStatusOk,
save = props.save,
setView = Reusable.fn(view => props.view.setState(view)),
- embedded = false,
+ embedded = false
).render
val newButton = NewButton(
@@ -76,43 +79,37 @@ object EditorTopBar {
val metalsButton = MetalsStatusIndicator(
props.metalsStatus,
props.toggleMetalsStatus,
- props.view.value,
+ props.view.value
).render
- val downloadButton =
- props.snippetId match {
- case Some(sid) =>
- DownloadButton(snippetId = sid).render
- case _ =>
- EmptyVdom
- }
-
- val embeddedModalButton =
- (props.snippetId, props.router) match {
- case (Some(sid), Some(router)) =>
- val url = router.urlFor(Page.fromSnippetId(sid)).value
-
- val content =
- s"""""".stripMargin
-
- val embeddedModal =
- CopyModal(
- isDarkTheme = props.isDarkTheme,
- title = "Share your Code Snippet",
- subtitle = "Copy and embed your code snippet",
- modalId = "embed-modal",
- content = content,
- isClosed = props.isEmbeddedModalClosed,
- close = props.closeEmbeddedModal
- ).render
-
- li(title := s"Embed", role := "button", cls := "btn", onClick --> props.openEmbeddedModal)(
- i(cls := "fa fa-code"),
- span("Embed"),
- embeddedModal
- )
- case _ => EmptyVdom
- }
+ val downloadButton = props.snippetId match {
+ case Some(sid) => DownloadButton(snippetId = sid).render
+ case _ => EmptyVdom
+ }
+
+ val embeddedModalButton = (props.snippetId, props.router) match {
+ case (Some(sid), Some(router)) =>
+ val url = router.urlFor(Page.fromSnippetId(sid)).value
+
+ val content = s"""""".stripMargin
+
+ val embeddedModal = CopyModal(
+ isDarkTheme = props.isDarkTheme,
+ title = "Share your Code Snippet",
+ subtitle = "Copy and embed your code snippet",
+ modalId = "embed-modal",
+ content = content,
+ isClosed = props.isEmbeddedModalClosed,
+ close = props.closeEmbeddedModal
+ ).render
+
+ li(title := s"Embed", role := "button", cls := "btn", onClick --> props.openEmbeddedModal)(
+ i(cls := "fa fa-code"),
+ span("Embed"),
+ embeddedModal
+ )
+ case _ => EmptyVdom
+ }
nav(cls := "editor-topbar", isDisabled)(
ul(cls := "editor-buttons")(
@@ -123,15 +120,15 @@ object EditorTopBar {
worksheetButton,
downloadButton,
embeddedModalButton,
- metalsButton,
+ metalsButton
)
)
}
- private val component =
- ScalaComponent
- .builder[EditorTopBar]("EditorTopBar")
- .render_P(render)
- .configure(Reusability.shouldComponentUpdate)
- .build
+ private val component = ScalaComponent
+ .builder[EditorTopBar]("EditorTopBar")
+ .render_P(render)
+ .configure(Reusability.shouldComponentUpdate)
+ .build
+
}
diff --git a/client/src/main/scala/com.olegych.scastie.client/components/EmbeddedOverlay.scala b/client/src/main/scala/com.olegych.scastie.client/components/EmbeddedOverlay.scala
index f54da0746..239255b28 100644
--- a/client/src/main/scala/com.olegych.scastie.client/components/EmbeddedOverlay.scala
+++ b/client/src/main/scala/com.olegych.scastie.client/components/EmbeddedOverlay.scala
@@ -6,10 +6,11 @@ import org.scalajs.dom
import vdom.all._
final case class EmbeddedOverlay(
- inputsHasChanged: Boolean,
- embeddedSnippetId: Option[SnippetId],
- serverUrl: Option[String],
- save: Reusable[CallbackTo[Option[SnippetId]]]) {
+ inputsHasChanged: Boolean,
+ embeddedSnippetId: Option[SnippetId],
+ serverUrl: Option[String],
+ save: Reusable[CallbackTo[Option[SnippetId]]]
+) {
@inline def render: VdomElement = EmbeddedOverlay.component(this)
}
@@ -27,7 +28,7 @@ object EmbeddedOverlay {
props.embeddedSnippetId match {
case Some(snippetId) if !props.inputsHasChanged => open(snippetId)
- case _ => props.save.asCBO.flatMap(open)
+ case _ => props.save.asCBO.flatMap(open)
}
}
@@ -40,7 +41,7 @@ object EmbeddedOverlay {
}
private val component = ScalaFnComponent
- .withHooks[EmbeddedOverlay]
- .renderWithReuse(render)
-}
+ .withHooks[EmbeddedOverlay]
+ .renderWithReuse(render)
+}
diff --git a/client/src/main/scala/com.olegych.scastie.client/components/FormatButton.scala b/client/src/main/scala/com.olegych.scastie.client/components/FormatButton.scala
index 28a4a6357..5c75d2eab 100644
--- a/client/src/main/scala/com.olegych.scastie.client/components/FormatButton.scala
+++ b/client/src/main/scala/com.olegych.scastie.client/components/FormatButton.scala
@@ -14,19 +14,19 @@ object FormatButton {
private def render(props: FormatButton): VdomElement = {
li(
title := s"Format Code (${EditorKeymaps.format.getName})",
- role := "button",
- cls := "btn",
- onClick --> props.formatCode,
+ role := "button",
+ cls := "btn",
+ onClick --> props.formatCode
)(
i(cls := "fa fa-align-left"),
span("Format")
)
}
- private val component =
- ScalaComponent
- .builder[FormatButton]("FormatButton")
- .render_P(render)
- .configure(Reusability.shouldComponentUpdate)
- .build
+ private val component = ScalaComponent
+ .builder[FormatButton]("FormatButton")
+ .render_P(render)
+ .configure(Reusability.shouldComponentUpdate)
+ .build
+
}
diff --git a/client/src/main/scala/com.olegych.scastie.client/components/HelpModal.scala b/client/src/main/scala/com.olegych.scastie.client/components/HelpModal.scala
index d28bccaa1..e9c9d9866 100644
--- a/client/src/main/scala/com.olegych.scastie.client/components/HelpModal.scala
+++ b/client/src/main/scala/com.olegych.scastie.client/components/HelpModal.scala
@@ -1,37 +1,34 @@
package com.olegych.scastie.client.components
+import com.olegych.scastie.client.components.editor.EditorKeymaps
import japgolly.scalajs.react._
import vdom.all._
-import com.olegych.scastie.client.components.editor.EditorKeymaps
final case class HelpModal(isDarkTheme: Boolean, isClosed: Boolean, close: Reusable[Callback]) {
@inline def render: VdomElement = HelpModal.component(this)
}
object HelpModal {
- implicit val reusability: Reusability[HelpModal] =
- Reusability.derive[HelpModal]
+ implicit val reusability: Reusability[HelpModal] = Reusability.derive[HelpModal]
private def render(props: HelpModal): VdomElement = {
- def generateATag(url: String, text: String) =
- a(href := url, target := "_blank", rel := "nofollow", text)
+ def generateATag(url: String, text: String) = a(href := url, target := "_blank", rel := "nofollow", text)
- val scastieGithub =
- generateATag("https://github.com/scalacenter/scastie", "scalacenter/scastie")
+ val scastieGithub = generateATag("https://github.com/scalacenter/scastie", "scalacenter/scastie")
val sublime = generateATag(
"https://sublime-text-unofficial-documentation.readthedocs.org/en/latest/reference/keyboard_shortcuts_osx.html",
"keyboard shortcuts."
)
- val scalafmtConfiguration =
- generateATag("https://scalameta.org/scalafmt/docs/configuration.html#disabling-or-customizing-formatting", "configuration section")
+ val scalafmtConfiguration = generateATag(
+ "https://scalameta.org/scalafmt/docs/configuration.html#disabling-or-customizing-formatting",
+ "configuration section"
+ )
- val originalScastie =
- generateATag("https://github.com/OlegYch/scastie_old", "GitHub")
+ val originalScastie = generateATag("https://github.com/OlegYch/scastie_old", "GitHub")
- val gitter =
- generateATag("https://gitter.im/scalacenter/scastie", "Gitter")
+ val gitter = generateATag("https://gitter.im/scalacenter/scastie", "Gitter")
Modal(
title = "Help about Scastie",
@@ -41,8 +38,8 @@ object HelpModal {
modalCss = TagMod(),
modalId = "long-help",
content = div(cls := "markdown-body")(
- p( "Scastie is an interactive playground for Scala with support for sbt configuration."),
- p( "Scastie editor supports Sublime Text ", sublime),
+ p("Scastie is an interactive playground for Scala with support for sbt configuration."),
+ p("Scastie editor supports Sublime Text ", sublime),
h2(s"Save (${EditorKeymaps.saveOrUpdate.getName})"),
p(
"Run and save your code."
@@ -58,7 +55,8 @@ object HelpModal {
h2(s"Format (${EditorKeymaps.format.getName})"),
p(
"The code formatting is done by scalafmt. You can configure the formatting with comments in your code. Read the ",
- scalafmtConfiguration),
+ scalafmtConfiguration
+ ),
h2(s"Worksheet"),
p(
"Enabled by default, the Worksheet Mode gives the value and the type of each line of your program. You can also add HTML blocks such as: ",
@@ -91,15 +89,14 @@ object HelpModal {
"Your saved code fragments will appear here and you'll be able to delete or share them."
),
h2("Feedback"),
- p( "You can join our ", gitter, " channel and send issues."),
+ p("You can join our ", gitter, " channel and send issues."),
h2("BuildInfo"),
- p( "It's available on Github at ")(
+ p("It's available on Github at ")(
scastieGithub,
br,
- " License: Apache 2",
+ " License: Apache 2"
),
p(
-
"Scastie is an original idea from Aleh Aleshka (OlegYch) "
)(
originalScastie
@@ -108,8 +105,8 @@ object HelpModal {
).render
}
- private val component =
- ScalaFnComponent
- .withHooks[HelpModal]
- .renderWithReuse(render)
+ private val component = ScalaFnComponent
+ .withHooks[HelpModal]
+ .renderWithReuse(render)
+
}
diff --git a/client/src/main/scala/com.olegych.scastie.client/components/LoginModal.scala b/client/src/main/scala/com.olegych.scastie.client/components/LoginModal.scala
index e42935d1a..42b9185a7 100644
--- a/client/src/main/scala/com.olegych.scastie.client/components/LoginModal.scala
+++ b/client/src/main/scala/com.olegych.scastie.client/components/LoginModal.scala
@@ -3,10 +3,8 @@ package components
import japgolly.scalajs.react._
import org.scalajs.dom
-
import vdom.all._
-
final case class LoginModal(
isDarkTheme: Boolean,
isClosed: Boolean,
@@ -20,9 +18,7 @@ object LoginModal {
implicit val reusability: Reusability[LoginModal] = Reusability.derive[LoginModal]
-
- def login: Callback =
- Callback(dom.window.location.pathname = "/login")
+ def login: Callback = Callback(dom.window.location.pathname = "/login")
def render(props: LoginModal): VdomElement = {
val theme = if (props.isDarkTheme) "dark" else "light"
@@ -37,21 +33,21 @@ object LoginModal {
content = TagMod(
button(onClick --> (login >> props.close), cls := "github-login")(
i(cls := "fa fa-github"),
- "Continue with GitHub",
+ "Continue with GitHub"
),
p(
"By signing in, you agree to our ",
- a(href := "#", onClick ==> (e => e.preventDefaultCB >> e.stopPropagationCB >> props.openPrivacyPolicyModal))("privacy policy"),
+ a(href := "#", onClick ==> (e => e.preventDefaultCB >> e.stopPropagationCB >> props.openPrivacyPolicyModal))(
+ "privacy policy"
+ ),
"."
)
)
).render
}
- private val component =
- ScalaFnComponent
- .withHooks[LoginModal]
- .renderWithReuse(render)
-
+ private val component = ScalaFnComponent
+ .withHooks[LoginModal]
+ .renderWithReuse(render)
}
diff --git a/client/src/main/scala/com.olegych.scastie.client/components/MainPanel.scala b/client/src/main/scala/com.olegych.scastie.client/components/MainPanel.scala
index 8bccde4c6..dbfd2661e 100644
--- a/client/src/main/scala/com.olegych.scastie.client/components/MainPanel.scala
+++ b/client/src/main/scala/com.olegych.scastie.client/components/MainPanel.scala
@@ -1,9 +1,9 @@
package com.olegych.scastie.client.components
+import com.olegych.scastie.client.components.editor.CodeEditor
import com.olegych.scastie.client.ScastieBackend
import com.olegych.scastie.client.ScastieState
import com.olegych.scastie.client.View
-import com.olegych.scastie.client.components.editor.CodeEditor
import japgolly.scalajs.react._
import japgolly.scalajs.react.vdom.all._
@@ -13,8 +13,7 @@ final case class MainPanel(state: ScastieState, backend: ScastieBackend, props:
}
object MainPanel {
- implicit val reusability: Reusability[MainPanel] =
- Reusability.derive[MainPanel]
+ implicit val reusability: Reusability[MainPanel] = Reusability.derive[MainPanel]
def render(in: MainPanel): VdomElement = {
import in._
@@ -26,160 +25,148 @@ object MainPanel {
val isStatusOk = state.status.isSbtOk
- val embeddedMenu =
- EmbeddedOverlay(
- inputsHasChanged = state.inputsHasChanged,
- embeddedSnippetId = props.embeddedSnippetId,
- serverUrl = props.serverUrl,
- save = backend.saveBlocking,
- ).render.when(props.isEmbedded)
-
- val consoleCssForEditor =
- (cls := "console-open").when(state.consoleState.consoleIsOpen)
-
- val codeSnippets =
- (props.router, state.user) match {
- case (Some(router), Some(user)) if state.view == View.CodeSnippets =>
- div(cls := "snippets-container inner-container")(
- CodeSnippets(
- isDarkTheme = state.isDarkTheme,
- view = state.view,
- user = user,
- router = router,
- isShareModalClosed = state.modalState.isShareModalClosed,
- closeShareModal = backend.closeShareModal,
- openShareModal = backend.openShareModal,
- loadProfile = backend.loadProfile,
- deleteSnippet = backend.deleteSnippet
- ).render
- )
- case _ => EmptyVdom
- }
-
- val editor =
- CodeEditor(
- visible = visible(View.Editor),
- isDarkTheme = state.isDarkTheme,
- isPresentationMode = state.isPresentationMode,
- isWorksheetMode = state.inputs.isWorksheetMode,
- isEmbedded = props.isEmbedded,
- showLineNumbers = state.showLineNumbers,
- value = state.inputs.code,
- attachedDoms = state.attachedDoms,
- instrumentations = state.outputs.instrumentations,
- compilationInfos = state.outputs.compilationInfos,
- runtimeError = state.outputs.runtimeError,
- saveOrUpdate = backend.saveOrUpdate,
- clear = backend.clear,
- openNewSnippetModal = backend.openNewSnippetModal,
- toggleHelp = backend.toggleHelpModal,
- toggleConsole = backend.toggleConsole,
- toggleLineNumbers = backend.toggleLineNumbers,
- togglePresentationMode = backend.togglePresentationMode,
- formatCode = backend.formatCode,
- codeChange = backend.codeChange,
- target = state.inputs.target,
- metalsStatus = state.metalsStatus,
- setMetalsStatus = backend.setMetalsStatus,
- dependencies = state.inputs.libraries
- ).render
-
- val console =
- Console(
- isOpen = state.consoleState.consoleIsOpen,
- isRunning = state.isRunning,
- isEmbedded = props.isEmbedded,
- consoleOutputs = state.outputs.consoleOutputs,
- run = backend.run,
- setView = backend.setViewReused,
- close = backend.closeConsole,
- open = backend.openConsole
- ).render
-
- val buildSettings =
- BuildSettings(
- visible = visible(View.BuildSettings),
- librariesFrom = state.inputs.librariesFrom,
- isDarkTheme = state.isDarkTheme,
- isBuildDefault = state.isBuildDefault,
- isResetModalClosed = state.modalState.isResetModalClosed,
- scalaTarget = state.inputs.target,
- sbtConfigExtra = state.inputs.sbtConfigExtra,
- sbtConfig = state.inputs.sbtConfigGenerated,
- sbtPluginsConfig = state.inputs.sbtPluginsConfigGenerated,
- setTarget = backend.setTarget,
- closeResetModal = backend.closeResetModal,
- resetBuild = backend.resetBuild,
- openResetModal = backend.openResetModal,
- sbtConfigChange = backend.sbtConfigChange,
- removeScalaDependency = backend.removeScalaDependency,
- updateDependencyVersion = backend.updateDependencyVersion,
- addScalaDependency = backend.addScalaDependency
- ).render
-
- val mobileBar =
- MobileBar(
- isRunning = state.isRunning,
- isStatusOk = isStatusOk,
- isDarkTheme = state.isDarkTheme,
- save = backend.saveOrUpdate,
- setView = backend.setViewReused,
- clear = backend.clear,
- isNewSnippetModalClosed = state.modalState.isNewSnippetModalClosed,
- openNewSnippetModal = backend.openNewSnippetModal,
- closeNewSnippetModal = backend.closeNewSnippetModal,
- newSnippet = backend.newSnippet,
- forceDesktop = backend.forceDesktop
- ).render
-
- val topBar =
- TopBar(
- backend.viewSnapshot(state.view),
- state.user,
- backend.openLoginModal
- ).render.unless(props.isEmbedded || state.isPresentationMode)
-
- val editorTopBar =
- EditorTopBar(
- clear = backend.clear,
- closeNewSnippetModal = backend.closeNewSnippetModal,
- closeEmbeddedModal = backend.closeEmbeddedModal,
- openEmbeddedModal = backend.openEmbeddedModal,
- formatCode = backend.formatCode,
- newSnippet = backend.newSnippet,
- openNewSnippetModal = backend.openNewSnippetModal,
- save = backend.saveOrUpdate,
- toggleWorksheetMode = backend.toggleWorksheetMode,
- router = props.router,
- inputsHasChanged = state.inputsHasChanged,
- isDarkTheme = state.isDarkTheme,
- isNewSnippetModalClosed = state.modalState.isNewSnippetModalClosed,
- isEmbeddedModalClosed = state.modalState.isEmbeddedClosed,
- isRunning = state.isRunning,
- isStatusOk = isStatusOk,
- snippetId = state.snippetId,
- user = state.user,
- view = backend.viewSnapshot(state.view),
- isWorksheetMode = state.inputs.isWorksheetMode,
- metalsStatus = state.metalsStatus,
- toggleMetalsStatus = backend.toggleMetalsStatus,
- scalaTarget = state.inputs.target
- ).render.unless(props.isEmbedded || state.isPresentationMode)
-
- val statusView =
- props.router match {
- case Some(router) =>
- Status(
- state = state.status,
+ val embeddedMenu = EmbeddedOverlay(
+ inputsHasChanged = state.inputsHasChanged,
+ embeddedSnippetId = props.embeddedSnippetId,
+ serverUrl = props.serverUrl,
+ save = backend.saveBlocking
+ ).render.when(props.isEmbedded)
+
+ val consoleCssForEditor = (cls := "console-open").when(state.consoleState.consoleIsOpen)
+
+ val codeSnippets = (props.router, state.user) match {
+ case (Some(router), Some(user)) if state.view == View.CodeSnippets =>
+ div(cls := "snippets-container inner-container")(
+ CodeSnippets(
+ isDarkTheme = state.isDarkTheme,
+ view = state.view,
+ user = user,
router = router,
- isAdmin = state.user.exists(_.isAdmin),
- inputs = state.inputs
+ isShareModalClosed = state.modalState.isShareModalClosed,
+ closeShareModal = backend.closeShareModal,
+ openShareModal = backend.openShareModal,
+ loadProfile = backend.loadProfile,
+ deleteSnippet = backend.deleteSnippet
).render
- case _ => EmptyVdom
- }
-
- val presentationModeClass =
- (cls := "presentation-mode").when(state.isPresentationMode)
+ )
+ case _ => EmptyVdom
+ }
+
+ val editor = CodeEditor(
+ visible = visible(View.Editor),
+ isDarkTheme = state.isDarkTheme,
+ isPresentationMode = state.isPresentationMode,
+ isWorksheetMode = state.inputs.isWorksheetMode,
+ isEmbedded = props.isEmbedded,
+ showLineNumbers = state.showLineNumbers,
+ value = state.inputs.code,
+ attachedDoms = state.attachedDoms,
+ instrumentations = state.outputs.instrumentations,
+ compilationInfos = state.outputs.compilationInfos,
+ runtimeError = state.outputs.runtimeError,
+ saveOrUpdate = backend.saveOrUpdate,
+ clear = backend.clear,
+ openNewSnippetModal = backend.openNewSnippetModal,
+ toggleHelp = backend.toggleHelpModal,
+ toggleConsole = backend.toggleConsole,
+ toggleLineNumbers = backend.toggleLineNumbers,
+ togglePresentationMode = backend.togglePresentationMode,
+ formatCode = backend.formatCode,
+ codeChange = backend.codeChange,
+ target = state.inputs.target,
+ metalsStatus = state.metalsStatus,
+ setMetalsStatus = backend.setMetalsStatus,
+ dependencies = state.inputs.libraries
+ ).render
+
+ val console = Console(
+ isOpen = state.consoleState.consoleIsOpen,
+ isRunning = state.isRunning,
+ isEmbedded = props.isEmbedded,
+ consoleOutputs = state.outputs.consoleOutputs,
+ run = backend.run,
+ setView = backend.setViewReused,
+ close = backend.closeConsole,
+ open = backend.openConsole
+ ).render
+
+ val buildSettings = BuildSettings(
+ visible = visible(View.BuildSettings),
+ librariesFrom = state.inputs.librariesFrom,
+ isDarkTheme = state.isDarkTheme,
+ isBuildDefault = state.isBuildDefault,
+ isResetModalClosed = state.modalState.isResetModalClosed,
+ scalaTarget = state.inputs.target,
+ sbtConfigExtra = state.inputs.sbtConfigExtra,
+ sbtConfig = state.inputs.sbtConfigGenerated,
+ sbtPluginsConfig = state.inputs.sbtPluginsConfigGenerated,
+ setTarget = backend.setTarget,
+ closeResetModal = backend.closeResetModal,
+ resetBuild = backend.resetBuild,
+ openResetModal = backend.openResetModal,
+ sbtConfigChange = backend.sbtConfigChange,
+ removeScalaDependency = backend.removeScalaDependency,
+ updateDependencyVersion = backend.updateDependencyVersion,
+ addScalaDependency = backend.addScalaDependency
+ ).render
+
+ val mobileBar = MobileBar(
+ isRunning = state.isRunning,
+ isStatusOk = isStatusOk,
+ isDarkTheme = state.isDarkTheme,
+ save = backend.saveOrUpdate,
+ setView = backend.setViewReused,
+ clear = backend.clear,
+ isNewSnippetModalClosed = state.modalState.isNewSnippetModalClosed,
+ openNewSnippetModal = backend.openNewSnippetModal,
+ closeNewSnippetModal = backend.closeNewSnippetModal,
+ newSnippet = backend.newSnippet,
+ forceDesktop = backend.forceDesktop
+ ).render
+
+ val topBar = TopBar(
+ backend.viewSnapshot(state.view),
+ state.user,
+ backend.openLoginModal
+ ).render.unless(props.isEmbedded || state.isPresentationMode)
+
+ val editorTopBar = EditorTopBar(
+ clear = backend.clear,
+ closeNewSnippetModal = backend.closeNewSnippetModal,
+ closeEmbeddedModal = backend.closeEmbeddedModal,
+ openEmbeddedModal = backend.openEmbeddedModal,
+ formatCode = backend.formatCode,
+ newSnippet = backend.newSnippet,
+ openNewSnippetModal = backend.openNewSnippetModal,
+ save = backend.saveOrUpdate,
+ toggleWorksheetMode = backend.toggleWorksheetMode,
+ router = props.router,
+ inputsHasChanged = state.inputsHasChanged,
+ isDarkTheme = state.isDarkTheme,
+ isNewSnippetModalClosed = state.modalState.isNewSnippetModalClosed,
+ isEmbeddedModalClosed = state.modalState.isEmbeddedClosed,
+ isRunning = state.isRunning,
+ isStatusOk = isStatusOk,
+ snippetId = state.snippetId,
+ user = state.user,
+ view = backend.viewSnapshot(state.view),
+ isWorksheetMode = state.inputs.isWorksheetMode,
+ metalsStatus = state.metalsStatus,
+ toggleMetalsStatus = backend.toggleMetalsStatus,
+ scalaTarget = state.inputs.target
+ ).render.unless(props.isEmbedded || state.isPresentationMode)
+
+ val statusView = props.router match {
+ case Some(router) => Status(
+ state = state.status,
+ router = router,
+ isAdmin = state.user.exists(_.isAdmin),
+ inputs = state.inputs
+ ).render
+ case _ => EmptyVdom
+ }
+
+ val presentationModeClass = (cls := "presentation-mode").when(state.isPresentationMode)
div(
cls := "main-panel",
@@ -208,10 +195,10 @@ object MainPanel {
}
- private val component =
- ScalaComponent
- .builder[MainPanel]("MainPanel")
- .render_P(render)
- .configure(Reusability.shouldComponentUpdate)
- .build
+ private val component = ScalaComponent
+ .builder[MainPanel]("MainPanel")
+ .render_P(render)
+ .configure(Reusability.shouldComponentUpdate)
+ .build
+
}
diff --git a/client/src/main/scala/com.olegych.scastie.client/components/MetalsStatusIndicator.scala b/client/src/main/scala/com.olegych.scastie.client/components/MetalsStatusIndicator.scala
index df738b681..bea6f199b 100644
--- a/client/src/main/scala/com.olegych.scastie.client/components/MetalsStatusIndicator.scala
+++ b/client/src/main/scala/com.olegych.scastie.client/components/MetalsStatusIndicator.scala
@@ -2,17 +2,16 @@ package com.olegych.scastie
package client
package components
-import japgolly.scalajs.react._
-
import scala.scalajs.js.annotation.JSImport
-import vdom.all._
+import japgolly.scalajs.react._
import scalajs.js
+import vdom.all._
final case class MetalsStatusIndicator(
- metalsStatus: MetalsStatus,
- toggleMetalsStatus: Reusable[Callback],
- view: View,
+ metalsStatus: MetalsStatus,
+ toggleMetalsStatus: Reusable[Callback],
+ view: View
) {
@inline def render: VdomElement = MetalsStatusIndicator.component(this)
}
@@ -21,16 +20,15 @@ final case class MetalsStatusIndicator(
@js.native
object MetalsLogo extends js.Any
-
object MetalsStatusIndicator {
def metalsLogo: String = MetalsLogo.asInstanceOf[String]
def getIndicatorIconClasses(status: MetalsStatus): String = {
status match {
- case MetalsLoading => "metals-loading fa-spinner fa-spin"
- case MetalsDisabled => "metals-disabled fa-circle metals-disabled"
- case MetalsReady => "metals-ready fa-circle metals-ready"
- case _: NetworkError => "fa-exclamation-circle"
+ case MetalsLoading => "metals-loading fa-spinner fa-spin"
+ case MetalsDisabled => "metals-disabled fa-circle metals-disabled"
+ case MetalsReady => "metals-ready fa-circle metals-ready"
+ case _: NetworkError => "fa-exclamation-circle"
case _: MetalsConfigurationError => "fa-exclamation-triangle"
}
}
@@ -38,9 +36,9 @@ object MetalsStatusIndicator {
private def render(props: MetalsStatusIndicator): VdomElement = {
li(
title := props.metalsStatus.info,
- role := "button",
- cls := "btn editor metals-status-indicator",
- onClick --> props.toggleMetalsStatus,
+ role := "button",
+ cls := "btn editor metals-status-indicator",
+ onClick --> props.toggleMetalsStatus
)(
img(src := metalsLogo),
span("Metals Status"),
@@ -48,8 +46,8 @@ object MetalsStatusIndicator {
)
}
- private val component =
- ScalaFnComponent
- .withHooks[MetalsStatusIndicator]
- .render(props => MetalsStatusIndicator.render(props))
+ private val component = ScalaFnComponent
+ .withHooks[MetalsStatusIndicator]
+ .render(props => MetalsStatusIndicator.render(props))
+
}
diff --git a/client/src/main/scala/com.olegych.scastie.client/components/MobileBar.scala b/client/src/main/scala/com.olegych.scastie.client/components/MobileBar.scala
index 1b8bb2c96..a6687a6ff 100644
--- a/client/src/main/scala/com.olegych.scastie.client/components/MobileBar.scala
+++ b/client/src/main/scala/com.olegych.scastie.client/components/MobileBar.scala
@@ -4,23 +4,24 @@ import com.olegych.scastie.client.View
import japgolly.scalajs.react._
import japgolly.scalajs.react.vdom.all._
-final case class MobileBar(isRunning: Boolean,
- isStatusOk: Boolean,
- isDarkTheme: Boolean,
- save: Reusable[Callback],
- setView: View ~=> Callback,
- isNewSnippetModalClosed: Boolean,
- clear: Reusable[Callback],
- openNewSnippetModal: Reusable[Callback],
- closeNewSnippetModal: Reusable[Callback],
- newSnippet: Reusable[Callback],
- forceDesktop: Reusable[Callback]) {
+final case class MobileBar(
+ isRunning: Boolean,
+ isStatusOk: Boolean,
+ isDarkTheme: Boolean,
+ save: Reusable[Callback],
+ setView: View ~=> Callback,
+ isNewSnippetModalClosed: Boolean,
+ clear: Reusable[Callback],
+ openNewSnippetModal: Reusable[Callback],
+ closeNewSnippetModal: Reusable[Callback],
+ newSnippet: Reusable[Callback],
+ forceDesktop: Reusable[Callback]
+) {
@inline def render: VdomElement = MobileBar.component(this)
}
object MobileBar {
- implicit val reusability: Reusability[MobileBar] =
- Reusability.derive[MobileBar]
+ implicit val reusability: Reusability[MobileBar] = Reusability.derive[MobileBar]
private def render(props: MobileBar): VdomElement = {
nav(cls := "editor-mobile")(
@@ -30,7 +31,7 @@ object MobileBar {
isStatusOk = props.isStatusOk,
save = props.save,
setView = props.setView,
- embedded = false,
+ embedded = false
).render,
NewButton(
isDarkTheme = props.isDarkTheme,
@@ -40,9 +41,9 @@ object MobileBar {
newSnippet = props.newSnippet
).render,
ClearButton(
- clear = props.clear,
- ).render,
- //this doesn't work too well, better use browsers 'request desktop site'
+ clear = props.clear
+ ).render
+ // this doesn't work too well, better use browsers 'request desktop site'
// DesktopButton(
// forceDesktop = props.forceDesktop
// ).render
@@ -50,10 +51,10 @@ object MobileBar {
)
}
- private val component =
- ScalaComponent
- .builder[MobileBar]("MobileBar")
- .render_P(render)
- .configure(Reusability.shouldComponentUpdate)
- .build
+ private val component = ScalaComponent
+ .builder[MobileBar]("MobileBar")
+ .render_P(render)
+ .configure(Reusability.shouldComponentUpdate)
+ .build
+
}
diff --git a/client/src/main/scala/com.olegych.scastie.client/components/NewButton.scala b/client/src/main/scala/com.olegych.scastie.client/components/NewButton.scala
index d8d312024..81850d052 100644
--- a/client/src/main/scala/com.olegych.scastie.client/components/NewButton.scala
+++ b/client/src/main/scala/com.olegych.scastie.client/components/NewButton.scala
@@ -3,27 +3,26 @@ package components
import com.olegych.scastie.client.components.editor.EditorKeymaps
import japgolly.scalajs.react._
-
import vdom.all._
final case class NewButton(
- isDarkTheme: Boolean,
- isNewSnippetModalClosed: Boolean,
- openNewSnippetModal: Reusable[Callback],
- closeNewSnippetModal: Reusable[Callback],
- newSnippet: Reusable[Callback]) {
+ isDarkTheme: Boolean,
+ isNewSnippetModalClosed: Boolean,
+ openNewSnippetModal: Reusable[Callback],
+ closeNewSnippetModal: Reusable[Callback],
+ newSnippet: Reusable[Callback]
+) {
@inline def render: VdomElement = NewButton.component(this)
}
object NewButton {
- implicit val reusability: Reusability[NewButton] =
- Reusability.derive[NewButton]
+ implicit val reusability: Reusability[NewButton] = Reusability.derive[NewButton]
def render(props: NewButton): VdomElement = {
li(
title := s"New code snippet (${EditorKeymaps.openNewSnippetModal.getName})",
- role := "button",
+ role := "button",
onClick --> props.openNewSnippetModal,
cls := "btn"
)(
@@ -42,10 +41,10 @@ object NewButton {
)
}
- private val component =
- ScalaComponent
- .builder[NewButton]("NewButton")
- .render_P(render)
- .configure(Reusability.shouldComponentUpdate)
- .build
+ private val component = ScalaComponent
+ .builder[NewButton]("NewButton")
+ .render_P(render)
+ .configure(Reusability.shouldComponentUpdate)
+ .build
+
}
diff --git a/client/src/main/scala/com.olegych.scastie.client/components/PrivacyPolicyModal.scala b/client/src/main/scala/com.olegych.scastie.client/components/PrivacyPolicyModal.scala
index e249f38a6..1fb9ad493 100644
--- a/client/src/main/scala/com.olegych.scastie.client/components/PrivacyPolicyModal.scala
+++ b/client/src/main/scala/com.olegych.scastie.client/components/PrivacyPolicyModal.scala
@@ -1,17 +1,17 @@
package com.olegych.scastie.client.components
+import scala.scalajs.js.annotation.JSImport
+
import japgolly.scalajs.react._
import scalajs.js
import vdom.all._
-import scala.scalajs.js.annotation.JSImport
final case class PrivacyPolicyModal(isDarkTheme: Boolean, isClosed: Boolean, close: Reusable[Callback]) {
@inline def render: VdomElement = PrivacyPolicyModal.component(this)
}
object PrivacyPolicyModal {
- implicit val reusability: Reusability[PrivacyPolicyModal] =
- Reusability.derive[PrivacyPolicyModal]
+ implicit val reusability: Reusability[PrivacyPolicyModal] = Reusability.derive[PrivacyPolicyModal]
@js.native
@JSImport("@scastieRoot/privacy-policy.md", "html")
@@ -31,8 +31,8 @@ object PrivacyPolicyModal {
).render
}
- private val component =
- ScalaFnComponent
- .withHooks[PrivacyPolicyModal]
- .renderWithReuse(render)
+ private val component = ScalaFnComponent
+ .withHooks[PrivacyPolicyModal]
+ .renderWithReuse(render)
+
}
diff --git a/client/src/main/scala/com.olegych.scastie.client/components/PrivacyPolicyPrompt.scala b/client/src/main/scala/com.olegych.scastie.client/components/PrivacyPolicyPrompt.scala
index a49392056..6a0e830d9 100644
--- a/client/src/main/scala/com.olegych.scastie.client/components/PrivacyPolicyPrompt.scala
+++ b/client/src/main/scala/com.olegych.scastie.client/components/PrivacyPolicyPrompt.scala
@@ -1,30 +1,25 @@
package com.olegych.scastie.client
package components
-
import japgolly.scalajs.react._
import org.scalajs.dom
-
import vdom.all._
-
// scheduled for removal 2023-04-30
@deprecated("Scheduled for removal", "2023-04-30")
final case class PrivacyPolicyPrompt(
- isDarkTheme: Boolean,
- isClosed: Boolean,
- acceptPrivacyPolicy: Reusable[Callback],
- refusePrivacyPolicy: Reusable[Callback],
- openPrivacyPolicyModal: Reusable[Callback]
- ) {
+ isDarkTheme: Boolean,
+ isClosed: Boolean,
+ acceptPrivacyPolicy: Reusable[Callback],
+ refusePrivacyPolicy: Reusable[Callback],
+ openPrivacyPolicyModal: Reusable[Callback]
+) {
@inline def render: VdomElement = PrivacyPolicyPrompt.component(this)
}
-
@deprecated("Scheduled for removal", "2023-04-30")
object PrivacyPolicyPrompt {
- implicit val reusability: Reusability[PrivacyPolicyPrompt] =
- Reusability.derive[PrivacyPolicyPrompt]
+ implicit val reusability: Reusability[PrivacyPolicyPrompt] = Reusability.derive[PrivacyPolicyPrompt]
def reloadWindow = Reusable.always(Callback { dom.window.location.reload() })
@@ -40,11 +35,15 @@ object PrivacyPolicyPrompt {
modalId = "privacy-policy-prompt",
content = TagMod(
div(cls := "modal-intro")(
- p("""With the introduction of privacy policy to Scastie, you have to decide
+ p(
+ """With the introduction of privacy policy to Scastie, you have to decide
| whether you want to keep your existing code snippets, or remove them all from our database.
| By keeping the snippets, you acknowledge that you have read and agreed
| to the privacy policy terms available """.stripMargin.stripLineEnd,
- a(href := "#", onClick ==> (e => e.preventDefaultCB >> e.stopPropagationCB >> props.openPrivacyPolicyModal))(
+ a(
+ href := "#",
+ onClick ==> (e => e.preventDefaultCB >> e.stopPropagationCB >> props.openPrivacyPolicyModal)
+ )(
"here"
),
"."
@@ -62,15 +61,13 @@ object PrivacyPolicyPrompt {
),
p(
"""If you do not explicitly ask us to keep your snippets before April 30th 2023, we will delete them all."""
- ),
+ )
),
ul(
li(onClick ==> (e => e.stopPropagationCB >> props.acceptPrivacyPolicy), cls := "btn")(
"Keep my existing snippets"
),
- li(onClick ==> (e =>
- e.stopPropagationCB >> props.refusePrivacyPolicy
- ), cls := "btn")(
+ li(onClick ==> (e => e.stopPropagationCB >> props.refusePrivacyPolicy), cls := "btn")(
"Delete my existing snippets"
)
)
@@ -78,8 +75,8 @@ object PrivacyPolicyPrompt {
).render
}
- private val component =
- ScalaFnComponent
- .withHooks[PrivacyPolicyPrompt]
- .renderWithReuse(render)
+ private val component = ScalaFnComponent
+ .withHooks[PrivacyPolicyPrompt]
+ .renderWithReuse(render)
+
}
diff --git a/client/src/main/scala/com.olegych.scastie.client/components/PromptModal.scala b/client/src/main/scala/com.olegych.scastie.client/components/PromptModal.scala
index b83cfbc76..94aff262a 100644
--- a/client/src/main/scala/com.olegych.scastie.client/components/PromptModal.scala
+++ b/client/src/main/scala/com.olegych.scastie.client/components/PromptModal.scala
@@ -3,26 +3,25 @@ package client
package components
import japgolly.scalajs.react._
-
import vdom.all._
final case class PromptModal(
- isDarkTheme: Boolean,
- modalText: String,
- modalId: String,
- isClosed: Boolean,
- close: Reusable[Callback],
- actionText: String,
- actionLabel: String,
- action: Reusable[Callback]) {
+ isDarkTheme: Boolean,
+ modalText: String,
+ modalId: String,
+ isClosed: Boolean,
+ close: Reusable[Callback],
+ actionText: String,
+ actionLabel: String,
+ action: Reusable[Callback]
+) {
@inline def render: VdomElement = PromptModal.component(this)
}
object PromptModal {
- implicit val reusability: Reusability[PromptModal] =
- Reusability.derive[PromptModal]
+ implicit val reusability: Reusability[PromptModal] = Reusability.derive[PromptModal]
private def render(props: PromptModal): VdomElement = {
Modal(
@@ -38,10 +37,7 @@ object PromptModal {
props.actionText
),
ul(
- li(onClick ==> (
- e => e.stopPropagationCB >> props.action >> props.close
- ),
- cls := "btn")(
+ li(onClick ==> (e => e.stopPropagationCB >> props.action >> props.close), cls := "btn")(
props.actionLabel
),
li(onClick ==> (e => e.stopPropagationCB >> props.close), cls := "btn")(
@@ -52,10 +48,10 @@ object PromptModal {
).render
}
- private val component =
- ScalaComponent
- .builder[PromptModal]("PrompModal")
- .render_P(render)
- .configure(Reusability.shouldComponentUpdate)
- .build
+ private val component = ScalaComponent
+ .builder[PromptModal]("PrompModal")
+ .render_P(render)
+ .configure(Reusability.shouldComponentUpdate)
+ .build
+
}
diff --git a/client/src/main/scala/com.olegych.scastie.client/components/RunButton.scala b/client/src/main/scala/com.olegych.scastie.client/components/RunButton.scala
index d43d912e0..ecfaf1ecf 100644
--- a/client/src/main/scala/com.olegych.scastie.client/components/RunButton.scala
+++ b/client/src/main/scala/com.olegych.scastie.client/components/RunButton.scala
@@ -3,27 +3,34 @@ package components
import com.olegych.scastie.client.components.editor.EditorKeymaps
import japgolly.scalajs.react._
-
import vdom.all._
-final case class RunButton(isRunning: Boolean, isStatusOk: Boolean, save: Reusable[Callback], setView: View ~=> Callback, embedded: Boolean) {
+final case class RunButton(
+ isRunning: Boolean,
+ isStatusOk: Boolean,
+ save: Reusable[Callback],
+ setView: View ~=> Callback,
+ embedded: Boolean
+) {
@inline def render: VdomElement = RunButton.component(this)
}
object RunButton {
- implicit val reusability: Reusability[RunButton] =
- Reusability.derive[RunButton]
+ implicit val reusability: Reusability[RunButton] = Reusability.derive[RunButton]
def render(props: RunButton): VdomElement = {
if (!props.isRunning) {
val runTitle =
- if (props.isStatusOk)
- s"Run (${EditorKeymaps.saveOrUpdate.getName})"
- else
- s"Run (${EditorKeymaps.saveOrUpdate.getName}) - warning: unknown status"
-
- li(onClick ==> { e => e.stopPropagationCB >> props.save }, role := "button", title := runTitle, cls := "btn run-button")(
+ if (props.isStatusOk) s"Run (${EditorKeymaps.saveOrUpdate.getName})"
+ else s"Run (${EditorKeymaps.saveOrUpdate.getName}) - warning: unknown status"
+
+ li(
+ onClick ==> { e => e.stopPropagationCB >> props.save },
+ role := "button",
+ title := runTitle,
+ cls := "btn run-button"
+ )(
i(cls := "fa fa-play"),
span("Run")
)
@@ -35,10 +42,10 @@ object RunButton {
}
}
- private val component =
- ScalaComponent
- .builder[RunButton]("RunButton")
- .render_P(render)
- .configure(Reusability.shouldComponentUpdate)
- .build
+ private val component = ScalaComponent
+ .builder[RunButton]("RunButton")
+ .render_P(render)
+ .configure(Reusability.shouldComponentUpdate)
+ .build
+
}
diff --git a/client/src/main/scala/com.olegych.scastie.client/components/ScaladexSearch.scala b/client/src/main/scala/com.olegych.scastie.client/components/ScaladexSearch.scala
index 315fbe9a9..4b658f08f 100644
--- a/client/src/main/scala/com.olegych.scastie.client/components/ScaladexSearch.scala
+++ b/client/src/main/scala/com.olegych.scastie.client/components/ScaladexSearch.scala
@@ -1,79 +1,76 @@
package com.olegych.scastie.client.components
+import scala.concurrent.Future
+
+import com.olegych.scastie.api._
import com.olegych.scastie.api.ScalaTarget.Jvm
import com.olegych.scastie.api.ScalaTarget.Scala3
-import com.olegych.scastie.api._
import com.olegych.scastie.buildinfo.BuildInfo
+import dom.{HTMLElement, HTMLInputElement}
+import dom.ext.KeyCode
import japgolly.scalajs.react._
import japgolly.scalajs.react.component.builder.Lifecycle.RenderScope
import org.scalajs.dom
import play.api.libs.json.Json
-
-import scala.concurrent.Future
-
-import vdom.all._
-import dom.ext.KeyCode
-import dom.{HTMLInputElement, HTMLElement}
-import scalajs.js.Thenable.Implicits._
import scalajs.concurrent.JSExecutionContext.Implicits.queue
+import scalajs.js.Thenable.Implicits._
+import vdom.all._
final case class ScaladexSearch(
- removeScalaDependency: ScalaDependency ~=> Callback,
- updateDependencyVersion: (ScalaDependency, String) ~=> Callback,
- addScalaDependency: (ScalaDependency, Project) ~=> Callback,
- librariesFrom: Map[ScalaDependency, Project],
- scalaTarget: ScalaTarget
+ removeScalaDependency: ScalaDependency ~=> Callback,
+ updateDependencyVersion: (ScalaDependency, String) ~=> Callback,
+ addScalaDependency: (ScalaDependency, Project) ~=> Callback,
+ librariesFrom: Map[ScalaDependency, Project],
+ scalaTarget: ScalaTarget
) {
@inline def render: VdomElement = ScaladexSearch.component(this)
}
object ScaladexSearch {
- implicit val propsReusability: Reusability[ScaladexSearch] =
- Reusability.derive[ScaladexSearch]
+ implicit val propsReusability: Reusability[ScaladexSearch] = Reusability.derive[ScaladexSearch]
- implicit val selectedReusability: Reusability[Selected] =
- Reusability.derive[Selected]
+ implicit val selectedReusability: Reusability[Selected] = Reusability.derive[Selected]
- implicit val stateReusability: Reusability[SearchState] =
- Reusability.derive[SearchState]
+ implicit val stateReusability: Reusability[SearchState] = Reusability.derive[SearchState]
private[ScaladexSearch] object SearchState {
+
def default: SearchState = SearchState(
query = "",
selectedIndex = 0,
projects = List.empty,
selecteds = List.empty
)
+
}
private[ScaladexSearch] case class Selected(
- project: Project,
- release: ScalaDependency,
- options: ReleaseOptions
+ project: Project,
+ release: ScalaDependency,
+ options: ReleaseOptions
) {
def matches(p: Project, artifact: String) = p == project && release.artifact == artifact
}
private[ScaladexSearch] case class SearchState(
- query: String,
- selectedIndex: Int,
- projects: List[(Project, ScalaTarget)],
- selecteds: List[Selected]
+ query: String,
+ selectedIndex: Int,
+ projects: List[(Project, ScalaTarget)],
+ selecteds: List[Selected]
) {
private val selectedProjectsArtifacts = selecteds
.map(selected => (selected.project, selected.release.artifact, None, selected.release.target))
.toSet
- val search: List[(Project, String, Option[String], ScalaTarget)] =
- projects
- .flatMap {
- case (project, target) => project.artifacts.map(artifact => (project, artifact, None, target))
- }
- .filter { projectAndArtifact =>
- !selectedProjectsArtifacts.contains(projectAndArtifact)
- }
+ val search: List[(Project, String, Option[String], ScalaTarget)] = projects
+ .flatMap { case (project, target) =>
+ project.artifacts.map(artifact => (project, artifact, None, target))
+ }
+ .filter { projectAndArtifact =>
+ !selectedProjectsArtifacts.contains(projectAndArtifact)
+ }
def removeSelected(selected: Selected): SearchState = {
copy(selecteds = selecteds.filterNot(_.release.matches(selected.release)))
@@ -86,7 +83,10 @@ object ScaladexSearch {
}
def updateVersion(selected: Selected, version: String): SearchState = {
- val updated = selected.copy(release = selected.release.copy(version = version), options = selected.options.copy(version = version))
+ val updated = selected.copy(
+ release = selected.release.copy(version = version),
+ options = selected.options.copy(version = version)
+ )
copy(
selecteds = selecteds.filterNot(_.release.matches(updated.release)) :+ updated
)
@@ -99,31 +99,31 @@ object ScaladexSearch {
def clearProjects: SearchState = {
copy(projects = List())
}
+
}
// private val scaladexBaseUrl = "http://localhost:8080"
private val scaladexBaseUrl = "https://index.scala-lang.org"
- private val scaladexApiUrl = scaladexBaseUrl + "/api"
+ private val scaladexApiUrl = scaladexBaseUrl + "/api"
- private implicit val projectOrdering: Ordering[Project] =
- Ordering.by { project: Project =>
- (project.organization, project.repository)
- }
+ private implicit val projectOrdering: Ordering[Project] = Ordering.by { project: Project =>
+ (project.organization, project.repository)
+ }
private implicit val scalaDependenciesOrdering: Ordering[ScalaDependency] =
Ordering.by { scalaDependency: ScalaDependency =>
scalaDependency.artifact
}
- private implicit val selectedOrdering: Ordering[Selected] =
- Ordering.by { selected: Selected =>
- (selected.project, selected.release)
- }
+ private implicit val selectedOrdering: Ordering[Selected] = Ordering.by { selected: Selected =>
+ (selected.project, selected.release)
+ }
private val projectListRef = Ref[HTMLElement]
private val searchInputRef = Ref[HTMLInputElement]
private[ScaladexSearch] class ScaladexSearchBackend(scope: BackendScope[ScaladexSearch, SearchState]) {
+
def keyDown(e: ReactKeyboardEventFromInput): Callback = {
if (e.keyCode == KeyCode.Down || e.keyCode == KeyCode.Up) {
@@ -146,18 +146,13 @@ object ScaladexSearch {
)
}
- def selectProject =
- scope.modState(
- s =>
- s.copy(
- selectedIndex = clamp(s.search.size, s.selectedIndex + diff)
- )
+ def selectProject = scope.modState(s =>
+ s.copy(
+ selectedIndex = clamp(s.search.size, s.selectedIndex + diff)
)
+ )
- def scrollToSelectedProject =
- scope.state.map(
- s => scrollToSelected(s.selectedIndex, s.search.size)
- )
+ def scrollToSelectedProject = scope.state.map(s => scrollToSelected(s.selectedIndex, s.search.size))
selectProject >>
e.preventDefaultCB >>
@@ -165,15 +160,15 @@ object ScaladexSearch {
} else if (e.keyCode == KeyCode.Enter) {
- def addArtifactIfInRange =
- for {
- state <- scope.state
- props <- scope.props
- _ <- if (0 <= state.selectedIndex && state.selectedIndex < state.search.size) {
+ def addArtifactIfInRange = for {
+ state <- scope.state
+ props <- scope.props
+ _ <-
+ if (0 <= state.selectedIndex && state.selectedIndex < state.search.size) {
val (p, a, v, t) = state.search(state.selectedIndex)
addArtifact((p, a, v), t, state)
} else Callback.empty
- } yield ()
+ } yield ()
addArtifactIfInRange >> Callback(searchInputRef.unsafeGet().focus())
} else {
@@ -181,57 +176,52 @@ object ScaladexSearch {
}
}
- def addArtifact(projectAndArtifact: (Project, String, Option[String]),
- target: ScalaTarget,
- state: SearchState,
- localOnly: Boolean = false): Callback = {
+ def addArtifact(
+ projectAndArtifact: (Project, String, Option[String]),
+ target: ScalaTarget,
+ state: SearchState,
+ localOnly: Boolean = false
+ ): Callback = {
val (project, artifact, version) = projectAndArtifact
if (state.selecteds.exists(_.matches(project, artifact))) Callback(())
- else
- Callback.future {
- fetchSelected(project, artifact, target, version).map {
- case Some(selected) if !state.selecteds.exists(_.release.matches(selected.release)) =>
- def addScalaDependencyLocal =
- scope.modState(_.addSelected(selected))
-
- def addScalaDependencyBackend =
- if (localOnly) Callback(()) else scope.props.flatMap(_.addScalaDependency((selected.release, selected.project)))
-
- addScalaDependencyBackend >> addScalaDependencyLocal
- case _ => Callback(())
- }
+ else Callback.future {
+ fetchSelected(project, artifact, target, version).map {
+ case Some(selected) if !state.selecteds.exists(_.release.matches(selected.release)) =>
+ def addScalaDependencyLocal = scope.modState(_.addSelected(selected))
+
+ def addScalaDependencyBackend =
+ if (localOnly) Callback(())
+ else scope.props.flatMap(_.addScalaDependency((selected.release, selected.project)))
+
+ addScalaDependencyBackend >> addScalaDependencyLocal
+ case _ => Callback(())
}
+ }
}
def removeSelected(selected: Selected): Callback = {
- def removeDependencyLocal =
- scope.modState(_.removeSelected(selected))
+ def removeDependencyLocal = scope.modState(_.removeSelected(selected))
- def removeDependecyBackend =
- scope.props.flatMap(_.removeScalaDependency(selected.release))
+ def removeDependecyBackend = scope.props.flatMap(_.removeScalaDependency(selected.release))
removeDependecyBackend >> removeDependencyLocal
}
def updateVersion(selected: Selected)(e: ReactEventFromInput): Callback = {
- val version = e.target.value
- def updateDependencyVersionLocal =
- scope.modState(_.updateVersion(selected, version))
+ val version = e.target.value
+ def updateDependencyVersionLocal = scope.modState(_.updateVersion(selected, version))
- def updateDependencyVersionBackend =
- scope.props.flatMap(
- _.updateDependencyVersion((selected.release, version))
- )
+ def updateDependencyVersionBackend = scope.props.flatMap(
+ _.updateDependencyVersion((selected.release, version))
+ )
updateDependencyVersionBackend >> updateDependencyVersionLocal
}
- def selectIndex(index: Int): Callback =
- scope.modState(s => s.copy(selectedIndex = index))
+ def selectIndex(index: Int): Callback = scope.modState(s => s.copy(selectedIndex = index))
- def resetQuery: Callback =
- scope.modState(s => s.copy(query = "", projects = Nil))
+ def resetQuery: Callback = scope.modState(s => s.copy(query = "", projects = Nil))
def setQuery(e: ReactEventFromInput): Callback = {
e.extract(_.target.value) { value =>
@@ -247,7 +237,7 @@ object ScaladexSearch {
val q = toQuery(t.scaladexRequest + ("q" -> searchState.query))
for {
response <- dom.fetch(scaladexApiUrl + "/search" + q)
- text <- response.text()
+ text <- response.text()
} yield {
Json.fromJson[List[Project]](Json.parse(text)).asOpt.getOrElse(Nil).map(_ -> t)
}
@@ -256,8 +246,7 @@ object ScaladexSearch {
val projsForThisTarget = queryAndParse(target)
val projects: Future[List[(Project, ScalaTarget)]] = target match {
// If scala3 but no scala 3 versions available, offer 2.13 artifacts
- case Scala3(_) =>
- projsForThisTarget.flatMap { ls =>
+ case Scala3(_) => projsForThisTarget.flatMap { ls =>
queryAndParse(Jvm(BuildInfo.latest213))
.map(arts213 => ls ::: arts213)
}
@@ -271,26 +260,25 @@ object ScaladexSearch {
}
for {
- props <- scope.props
+ props <- scope.props
searchState <- scope.state
- _ <- fetch(props.scalaTarget, searchState)
+ _ <- fetch(props.scalaTarget, searchState)
} yield ()
}
- private def toQuery(in: Map[String, String]): String =
- in.map { case (k, v) => s"$k=$v" }.mkString("?", "&", "")
+ private def toQuery(in: Map[String, String]): String = in.map { case (k, v) => s"$k=$v" }.mkString("?", "&", "")
private def fetchSelected(project: Project, artifact: String, target: ScalaTarget, version: Option[String]) = {
val query = toQuery(
Map(
"organization" -> project.organization,
- "repository" -> project.repository
+ "repository" -> project.repository
) ++ target.scaladexRequest
)
for {
response <- dom.fetch(scaladexApiUrl + "/project" + query)
- text <- response.text()
+ text <- response.text()
} yield {
Json.fromJson[ReleaseOptions](Json.parse(text)).asOpt.map { options =>
{
@@ -300,46 +288,45 @@ object ScaladexSearch {
groupId = options.groupId,
artifact = artifact,
target = target,
- version = version.getOrElse(options.version),
+ version = version.getOrElse(options.version)
),
- options = options,
+ options = options
)
}
}
}
}
+
}
private def render(
- scope: RenderScope[ScaladexSearch, SearchState, ScaladexSearchBackend],
- props: ScaladexSearch,
- searchState: SearchState
+ scope: RenderScope[ScaladexSearch, SearchState, ScaladexSearchBackend],
+ props: ScaladexSearch,
+ searchState: SearchState
): VdomElement = {
- def selectedIndex(index: Int, selected: Int) =
- (cls := "selected").when(index == selected)
-
- def renderProject(project: Project,
- artifact: String,
- scalaTarget: ScalaTarget,
- selected: TagMod,
- handlers: TagMod = EmptyVdom,
- remove: TagMod = EmptyVdom,
- options: TagMod = EmptyVdom) = {
+ def selectedIndex(index: Int, selected: Int) = (cls := "selected").when(index == selected)
+
+ def renderProject(
+ project: Project,
+ artifact: String,
+ scalaTarget: ScalaTarget,
+ selected: TagMod,
+ handlers: TagMod = EmptyVdom,
+ remove: TagMod = EmptyVdom,
+ options: TagMod = EmptyVdom
+ ) = {
import project._
val common = TagMod(title := organization, cls := "logo")
- val artifact2 =
- artifact
- .replace(project.repository + "-", "")
- .replace(project.repository, "")
+ val artifact2 = artifact
+ .replace(project.repository + "-", "")
+ .replace(project.repository, "")
val label =
- if (project.repository != artifact)
- s"${project.repository} / $artifact2"
+ if (project.repository != artifact) s"${project.repository} / $artifact2"
else artifact
- val scaladexLink =
- s"https://scaladex.scala-lang.org/$organization/$repository/$artifact"
+ val scaladexLink = s"https://scaladex.scala-lang.org/$organization/$repository/$artifact"
div(cls := "result", selected, handlers)(
a(cls := "scaladexresult", href := scaladexLink, target := "_blank")(
@@ -357,7 +344,7 @@ object ScaladexSearch {
if (scalaTarget.binaryScalaVersion != props.scalaTarget.binaryScalaVersion)
span(cls := "artifact")(s"(Scala ${scalaTarget.binaryScalaVersion} artifacts)")
else ""
- ),
+ )
)
}
@@ -366,7 +353,7 @@ object ScaladexSearch {
select(
selected.options.versions.reverse.map(v => option(value := v)(v)).toTagMod,
value := selected.release.version,
- onChange ==> scope.backend.updateVersion(selected),
+ onChange ==> scope.backend.updateVersion(selected)
)
)
}
@@ -404,9 +391,9 @@ object ScaladexSearch {
added,
div(cls := "search-input")(
input.search.withRef(searchInputRef)(
- cls := "search-query",
+ cls := "search-query",
placeholder := "Search for 'cats'",
- value := searchState.query,
+ value := searchState.query,
onChange ==> scope.backend.setQuery,
onKeyDown ==> scope.backend.keyDown
),
@@ -417,34 +404,33 @@ object ScaladexSearch {
)
),
div.withRef(projectListRef)(cls := "results", displayResults)(
- searchState.search.zipWithIndex.map {
- case ((project, artifact, version, target), index) =>
- renderProject(
- project,
- artifact,
- target,
- selected = selectedIndex(index, searchState.selectedIndex),
- handlers = TagMod(
- onClick --> scope.backend.addArtifact((project, artifact, version), target, scope.state),
- onMouseOver --> scope.backend.selectIndex(index)
- )
+ searchState.search.zipWithIndex.map { case ((project, artifact, version, target), index) =>
+ renderProject(
+ project,
+ artifact,
+ target,
+ selected = selectedIndex(index, searchState.selectedIndex),
+ handlers = TagMod(
+ onClick --> scope.backend.addArtifact((project, artifact, version), target, scope.state),
+ onMouseOver --> scope.backend.selectIndex(index)
)
+ )
}.toTagMod
)
)
}
- private val component =
- ScalaComponent
- .builder[ScaladexSearch]("Scaladex Search")
- .initialState(SearchState.default)
- .backend(new ScaladexSearchBackend(_))
- .renderPS(render)
- .componentWillReceiveProps { x =>
- Callback.traverse(x.nextProps.librariesFrom.toList.sortBy(_._1.artifact)) { lib =>
- x.backend.addArtifact((lib._2, lib._1.artifact, Some(lib._1.version)), lib._1.target, x.state, localOnly = true)
- }
+ private val component = ScalaComponent
+ .builder[ScaladexSearch]("Scaladex Search")
+ .initialState(SearchState.default)
+ .backend(new ScaladexSearchBackend(_))
+ .renderPS(render)
+ .componentWillReceiveProps { x =>
+ Callback.traverse(x.nextProps.librariesFrom.toList.sortBy(_._1.artifact)) { lib =>
+ x.backend.addArtifact((lib._2, lib._1.artifact, Some(lib._1.version)), lib._1.target, x.state, localOnly = true)
}
- .configure(Reusability.shouldComponentUpdate)
- .build
+ }
+ .configure(Reusability.shouldComponentUpdate)
+ .build
+
}
diff --git a/client/src/main/scala/com.olegych.scastie.client/components/Scastie.scala b/client/src/main/scala/com.olegych.scastie.client/components/Scastie.scala
index 2982bc82c..ee09ffced 100644
--- a/client/src/main/scala/com.olegych.scastie.client/components/Scastie.scala
+++ b/client/src/main/scala/com.olegych.scastie.client/components/Scastie.scala
@@ -1,5 +1,7 @@
package com.olegych.scastie.client.components
+import java.util.UUID
+
import com.olegych.scastie.api._
import com.olegych.scastie.client._
import japgolly.scalajs.react._
@@ -9,44 +11,40 @@ import japgolly.scalajs.react.vdom.all._
import org.scalajs.dom
import org.scalajs.dom.HTMLScriptElement
-import java.util.UUID
-
final case class Scastie(
- router: Option[RouterCtl[Page]],
- private val scastieId: UUID,
- private val snippetId: Option[SnippetId],
- private val oldSnippetId: Option[Int],
- private val embedded: Option[EmbeddedOptions],
- private val targetType: Option[ScalaTargetType],
- private val tryLibrary: Option[(ScalaDependency, Project)],
- private val code: Option[String],
- private val inputs: Option[Inputs],
+ router: Option[RouterCtl[Page]],
+ private val scastieId: UUID,
+ private val snippetId: Option[SnippetId],
+ private val oldSnippetId: Option[Int],
+ private val embedded: Option[EmbeddedOptions],
+ private val targetType: Option[ScalaTargetType],
+ private val tryLibrary: Option[(ScalaDependency, Project)],
+ private val code: Option[String],
+ private val inputs: Option[Inputs]
) {
@inline def render = Scastie.component(serverUrl, scastieId)(this)
def serverUrl: Option[String] = embedded.map(_.serverUrl)
- def isEmbedded: Boolean = embedded.isDefined
- //todo not sure how is it different from regular snippet id
+ def isEmbedded: Boolean = embedded.isDefined
+ // todo not sure how is it different from regular snippet id
def embeddedSnippetId: Option[SnippetId] = embedded.flatMap(_.snippetId)
}
object Scastie {
- implicit val scastieReuse: Reusability[Scastie] =
- Reusability.derive[Scastie]
-
- def default(router: RouterCtl[Page]): Scastie =
- Scastie(
- scastieId = UUID.randomUUID(),
- router = Some(router),
- snippetId = None,
- oldSnippetId = None,
- embedded = None,
- targetType = None,
- tryLibrary = None,
- code = None,
- inputs = None,
- )
+ implicit val scastieReuse: Reusability[Scastie] = Reusability.derive[Scastie]
+
+ def default(router: RouterCtl[Page]): Scastie = Scastie(
+ scastieId = UUID.randomUUID(),
+ router = Some(router),
+ snippetId = None,
+ oldSnippetId = None,
+ embedded = None,
+ targetType = None,
+ tryLibrary = None,
+ code = None,
+ inputs = None
+ )
private def setTitle(state: ScastieState, props: Scastie) = {
def scastieCode = if (state.inputs.code.isEmpty) "Scastie" else state.inputs.code + " - Scastie"
@@ -62,16 +60,15 @@ object Scastie {
}
private def render(
- scope: RenderScope[Scastie, ScastieState, ScastieBackend],
- props: Scastie,
- state: ScastieState
+ scope: RenderScope[Scastie, ScastieState, ScastieBackend],
+ props: Scastie,
+ state: ScastieState
): VdomElement = {
val theme =
if (state.isDarkTheme) "dark"
else "light"
- val forceDesktopClass =
- (cls := "force-desktop").when(state.isDesktopForced)
+ val forceDesktopClass = (cls := "force-desktop").when(state.isDesktopForced)
div(cls := s"app $theme", forceDesktopClass)(
SideBar(
@@ -97,70 +94,61 @@ object Scastie {
isDarkTheme = state.isDarkTheme,
isClosed = state.modalState.isLoginModalClosed,
close = scope.backend.closeLoginModal,
- openPrivacyPolicyModal = scope.backend.openPrivacyPolicyModal,
+ openPrivacyPolicyModal = scope.backend.openPrivacyPolicyModal
).render,
PrivacyPolicyPrompt(
isDarkTheme = state.isDarkTheme,
isClosed = state.modalState.isPrivacyPolicyPromptClosed,
acceptPrivacyPolicy = scope.backend.acceptPolicy,
refusePrivacyPolicy = scope.backend.refusePrivacyPolicy,
- openPrivacyPolicyModal = scope.backend.openPrivacyPolicyModal,
+ openPrivacyPolicyModal = scope.backend.openPrivacyPolicyModal
).render,
PrivacyPolicyModal(
isDarkTheme = state.isDarkTheme,
isClosed = state.modalState.isPrivacyPolicyModalClosed,
close = scope.backend.closePrivacyPolicyModal
- ).render,
+ ).render
)
}
private def start(props: Scastie, backend: ScastieBackend): Callback = {
- val initialState =
- props.embedded match {
- case None => {
- props.snippetId match {
- case Some(snippetId) =>
- backend.loadSnippet(snippetId)
-
- case None =>
- props.oldSnippetId match {
- case Some(id) =>
- backend.loadOldSnippet(id)
-
- case None =>
- Callback.traverseOption(LocalStorage.load) { state =>
- backend.scope.modState { _ =>
- state
- .setRunning(false)
- .setCleanInputs
- .resetScalajs
- }
+ val initialState = props.embedded match {
+ case None => {
+ props.snippetId match {
+ case Some(snippetId) => backend.loadSnippet(snippetId)
+
+ case None => props.oldSnippetId match {
+ case Some(id) => backend.loadOldSnippet(id)
+
+ case None => Callback.traverseOption(LocalStorage.load) { state =>
+ backend.scope.modState { _ =>
+ state
+ .setRunning(false)
+ .setCleanInputs
+ .resetScalajs
}
- }
- }
+ }
+ }
}
- case Some(embededOptions) => {
- val setInputs =
- (embededOptions.snippetId, embededOptions.inputs) match {
- case (Some(snippetId), _) =>
- backend.loadSnippet(snippetId)
-
- case (_, Some(inputs)) =>
- backend.scope.modState(_.setInputs(inputs))
+ }
+ case Some(embededOptions) => {
+ val setInputs = (embededOptions.snippetId, embededOptions.inputs) match {
+ case (Some(snippetId), _) => backend.loadSnippet(snippetId)
- case _ => Callback.empty
- }
+ case (_, Some(inputs)) => backend.scope.modState(_.setInputs(inputs))
- val setTheme =
- embededOptions.theme match {
- case Some("dark") => backend.scope.modState(_.setTheme(dark = true))
- case Some("light") => backend.scope.modState(_.setTheme(dark = false))
- case _ => Callback(())
- }
+ case _ => Callback.empty
+ }
- setInputs >> setTheme
+ val setTheme = embededOptions.theme match {
+ case Some("dark") => backend.scope.modState(_.setTheme(dark = true))
+ case Some("light") => backend.scope.modState(_.setTheme(dark = false))
+ case _ => Callback(())
}
+
+ setInputs >> setTheme
}
+ }
initialState >> backend.loadUser
}
@@ -190,20 +178,19 @@ object Scastie {
val scalaJsRunScriptElement = createScript(scalaJsRunId)
println("== Running Scala.js ==")
- val scalaJsScript =
- s"""|try {
- | var main = new ScastiePlaygroundMain();
- | scastie.ClientMain.signal(
- | main.result,
- | main.attachedElements,
- | "$scastieId"
- | );
- |} catch (e) {
- | scastie.ClientMain.error(
- | e,
- | "$scastieId"
- | );
- |}""".stripMargin
+ val scalaJsScript = s"""|try {
+ | var main = new ScastiePlaygroundMain();
+ | scastie.ClientMain.signal(
+ | main.result,
+ | main.attachedElements,
+ | "$scastieId"
+ | );
+ |} catch (e) {
+ | scastie.ClientMain.error(
+ | e,
+ | "$scastieId"
+ | );
+ |}""".stripMargin
scalaJsRunScriptElement.innerHTML = scalaJsScript
}
@@ -215,8 +202,8 @@ object Scastie {
state.snippetState.scalaJsContent.foreach { content =>
println("== Loading Scala.js! ==")
val scalaJsScriptElement = createScript(scalaJsId)
- val fixedContent = playgroundMainRegex.replaceAllIn(content, "var ScastiePlaygroundMain")
- val scriptTextNode = dom.document.createTextNode(fixedContent)
+ val fixedContent = playgroundMainRegex.replaceAllIn(content, "var ScastiePlaygroundMain")
+ val scriptTextNode = dom.document.createTextNode(fixedContent)
scalaJsScriptElement.appendChild(scriptTextNode)
runScalaJs()
}
@@ -224,87 +211,83 @@ object Scastie {
}
}
- private def component(serverUrl: Option[String], scastieId: UUID) =
- ScalaComponent
- .builder[Scastie]("Scastie")
- .initialStateFromProps { props =>
- val state = {
- val scheme = LocalStorage.load.map(_.isDarkTheme)
- val loadedState = ScastieState.default(props.isEmbedded)
- val loadedStateWithScheme = scheme.map(theme => loadedState.copy(isDarkTheme = theme)).getOrElse(loadedState)
- if (!props.isEmbedded) {
- loadedStateWithScheme
- } else {
- loadedStateWithScheme.setCleanInputs.clearOutputs
- }
- }
-
- val state1 =
- props.targetType match {
- case Some(targetType) => {
- val state0 =
- state.setTarget(targetType.defaultScalaTarget)
-
- if (targetType == ScalaTargetType.Scala3) {
- state0.setCode(ScalaTarget.Scala3.defaultCode)
- } else {
- state0
- }
- }
- case _ => state
- }
-
- val state2 = props.tryLibrary match {
- case Some(dependency) =>
- state1
- .setTarget(dependency._1.target)
- .addScalaDependency(dependency._1, dependency._2)
- case _ => state1
+ private def component(serverUrl: Option[String], scastieId: UUID) = ScalaComponent
+ .builder[Scastie]("Scastie")
+ .initialStateFromProps { props =>
+ val state = {
+ val scheme = LocalStorage.load.map(_.isDarkTheme)
+ val loadedState = ScastieState.default(props.isEmbedded)
+ val loadedStateWithScheme = scheme.map(theme => loadedState.copy(isDarkTheme = theme)).getOrElse(loadedState)
+ if (!props.isEmbedded) {
+ loadedStateWithScheme
+ } else {
+ loadedStateWithScheme.setCleanInputs.clearOutputs
}
+ }
- val state3 = props.code match {
- case Some(code) => state2.setCode(code)
- case _ => state2
- }
+ val state1 = props.targetType match {
+ case Some(targetType) => {
+ val state0 = state.setTarget(targetType.defaultScalaTarget)
- props.inputs match {
- case Some(inputs) => state3.setInputs(inputs)
- case _ => state3
+ if (targetType == ScalaTargetType.Scala3) {
+ state0.setCode(ScalaTarget.Scala3.defaultCode)
+ } else {
+ state0
+ }
}
+ case _ => state
}
- .backend(ScastieBackend(scastieId, serverUrl, _))
- .renderPS(render)
- .componentWillMount { current =>
- start(current.props, current.backend) >>
- setTitle(current.state, current.props) >>
- current.backend.closeNewSnippetModal >>
- current.backend.closeResetModal >>
- current.backend.connectStatus.when_(!current.props.isEmbedded)
- }
- .componentWillUnmount { current =>
- current.backend.disconnectStatus.when_(!current.props.isEmbedded) >>
- current.backend.unsubscribeGlobal
+
+ val state2 = props.tryLibrary match {
+ case Some(dependency) => state1
+ .setTarget(dependency._1.target)
+ .addScalaDependency(dependency._1, dependency._2)
+ case _ => state1
}
- .componentDidUpdate { scope =>
- setTitle(scope.prevState, scope.currentProps) >>
- scope.modState(_.scalaJsScriptLoaded) >>
- executeScalaJs(scastieId, scope.currentState)
+
+ val state3 = props.code match {
+ case Some(code) => state2.setCode(code)
+ case _ => state2
}
- .componentWillReceiveProps { scope =>
- val next = scope.nextProps.snippetId
- val current = scope.currentProps.snippetId
- val state = scope.state
- val backend = scope.backend
-
- val loadSnippet: CallbackOption[Unit] =
- for {
- snippetId <- CallbackOption.option(next)
- _ <- CallbackOption.require(next != current)
- _ <- backend.loadSnippet(snippetId).toCBO >> backend.setView(View.Editor)
- } yield ()
-
- setTitle(state, scope.nextProps) >> loadSnippet.toCallback
+
+ props.inputs match {
+ case Some(inputs) => state3.setInputs(inputs)
+ case _ => state3
}
- .configure(Reusability.shouldComponentUpdate)
- .build
+ }
+ .backend(ScastieBackend(scastieId, serverUrl, _))
+ .renderPS(render)
+ .componentWillMount { current =>
+ start(current.props, current.backend) >>
+ setTitle(current.state, current.props) >>
+ current.backend.closeNewSnippetModal >>
+ current.backend.closeResetModal >>
+ current.backend.connectStatus.when_(!current.props.isEmbedded)
+ }
+ .componentWillUnmount { current =>
+ current.backend.disconnectStatus.when_(!current.props.isEmbedded) >>
+ current.backend.unsubscribeGlobal
+ }
+ .componentDidUpdate { scope =>
+ setTitle(scope.prevState, scope.currentProps) >>
+ scope.modState(_.scalaJsScriptLoaded) >>
+ executeScalaJs(scastieId, scope.currentState)
+ }
+ .componentWillReceiveProps { scope =>
+ val next = scope.nextProps.snippetId
+ val current = scope.currentProps.snippetId
+ val state = scope.state
+ val backend = scope.backend
+
+ val loadSnippet: CallbackOption[Unit] = for {
+ snippetId <- CallbackOption.option(next)
+ _ <- CallbackOption.require(next != current)
+ _ <- backend.loadSnippet(snippetId).toCBO >> backend.setView(View.Editor)
+ } yield ()
+
+ setTitle(state, scope.nextProps) >> loadSnippet.toCallback
+ }
+ .configure(Reusability.shouldComponentUpdate)
+ .build
+
}
diff --git a/client/src/main/scala/com.olegych.scastie.client/components/SideBar.scala b/client/src/main/scala/com.olegych.scastie.client/components/SideBar.scala
index 5c7a93fc3..48741fe47 100644
--- a/client/src/main/scala/com.olegych.scastie.client/components/SideBar.scala
+++ b/client/src/main/scala/com.olegych.scastie.client/components/SideBar.scala
@@ -2,14 +2,13 @@ package com.olegych.scastie
package client
package components
-import com.olegych.scastie.api._
+import scala.scalajs.js
-import japgolly.scalajs.react._
-import vdom.all._
+import com.olegych.scastie.api._
import extra._
-
-import scala.scalajs.js
+import japgolly.scalajs.react._
import js.annotation._
+import vdom.all._
@JSImport("@resources/images/icon-scastie.png", JSImport.Default)
@js.native
@@ -20,24 +19,25 @@ object ScastieLogo extends js.Any
object Placeholder extends js.Any
object Assets {
- def logo: String = ScastieLogo.asInstanceOf[String]
+ def logo: String = ScastieLogo.asInstanceOf[String]
def placeholder: String = Placeholder.asInstanceOf[String]
}
-final case class SideBar(isDarkTheme: Boolean,
- status: StatusState,
- inputs: Inputs,
- toggleTheme: Reusable[Callback],
- view: StateSnapshot[View],
- openHelpModal: Reusable[Callback],
- openPrivacyPolicyModal: Reusable[Callback]) {
+final case class SideBar(
+ isDarkTheme: Boolean,
+ status: StatusState,
+ inputs: Inputs,
+ toggleTheme: Reusable[Callback],
+ view: StateSnapshot[View],
+ openHelpModal: Reusable[Callback],
+ openPrivacyPolicyModal: Reusable[Callback]
+) {
@inline def render: VdomElement = SideBar.component(this)
}
object SideBar {
- implicit val reusability: Reusability[SideBar] =
- Reusability.derive[SideBar]
+ implicit val reusability: Reusability[SideBar] = Reusability.derive[SideBar]
private def render(props: SideBar): VdomElement = {
val toggleThemeLabel =
@@ -48,11 +48,15 @@ object SideBar {
if (props.isDarkTheme) "fa fa-sun-o"
else "fa fa-moon-o"
- val themeButton =
- li(onClick --> props.toggleTheme, role := "button", title := s"Select $toggleThemeLabel Theme (F2)", cls := "btn")(
- i(cls := s"fa $selectedIcon"),
- span(toggleThemeLabel)
- )
+ val themeButton = li(
+ onClick --> props.toggleTheme,
+ role := "button",
+ title := s"Select $toggleThemeLabel Theme (F2)",
+ cls := "btn"
+ )(
+ i(cls := s"fa $selectedIcon"),
+ span(toggleThemeLabel)
+ )
val privacyPolicyButton =
li(onClick --> props.openPrivacyPolicyModal, role := "button", title := "Show privacy policy", cls := "btn")(
@@ -60,26 +64,26 @@ object SideBar {
span("Privacy Policy")
)
- val helpButton =
- li(onClick --> props.openHelpModal, role := "button", title := "Show help Menu", cls := "btn")(
- i(cls := "fa fa-question-circle"),
- span("Help")
- )
+ val helpButton = li(onClick --> props.openHelpModal, role := "button", title := "Show help Menu", cls := "btn")(
+ i(cls := "fa fa-question-circle"),
+ span("Help")
+ )
val runnersStatusButton = {
- val (statusIcon, statusClass, statusLabel) =
- props.status.sbtRunnerCount match {
- case None =>
- ("fa-times-circle", "status-unknown", "Unknown")
+ val (statusIcon, statusClass, statusLabel) = props.status.sbtRunnerCount match {
+ case None => ("fa-times-circle", "status-unknown", "Unknown")
- case Some(0) =>
- ("fa-times-circle", "status-down", "Down")
+ case Some(0) => ("fa-times-circle", "status-down", "Down")
- case Some(_) =>
- ("fa-check-circle", "status-up", "Up")
- }
+ case Some(_) => ("fa-check-circle", "status-up", "Up")
+ }
- li(onClick --> props.view.setState(View.Status), role := "button", title := "Show runners status", cls := s"btn $statusClass")(
+ li(
+ onClick --> props.view.setState(View.Status),
+ role := "button",
+ title := "Show runners status",
+ cls := s"btn $statusClass"
+ )(
i(cls := s"fa $statusIcon"),
span(statusLabel)
)
@@ -121,10 +125,10 @@ object SideBar {
)
}
- private val component =
- ScalaComponent
- .builder[SideBar]("SideBar")
- .render_P(render)
- .configure(Reusability.shouldComponentUpdate)
- .build
+ private val component = ScalaComponent
+ .builder[SideBar]("SideBar")
+ .render_P(render)
+ .configure(Reusability.shouldComponentUpdate)
+ .build
+
}
diff --git a/client/src/main/scala/com.olegych.scastie.client/components/Status.scala b/client/src/main/scala/com.olegych.scastie.client/components/Status.scala
index ffcb2b0d0..95dc9323b 100644
--- a/client/src/main/scala/com.olegych.scastie.client/components/Status.scala
+++ b/client/src/main/scala/com.olegych.scastie.client/components/Status.scala
@@ -4,10 +4,9 @@ import com.olegych.scastie.api.Inputs
import com.olegych.scastie.api.TaskId
import com.olegych.scastie.client.Page
import com.olegych.scastie.client.StatusState
+import extra.router._
import japgolly.scalajs.react._
-
import vdom.all._
-import extra.router._
final case class Status(state: StatusState, router: RouterCtl[Page], isAdmin: Boolean, inputs: Inputs) {
@inline def render: VdomElement = Status.component(this)
@@ -15,8 +14,7 @@ final case class Status(state: StatusState, router: RouterCtl[Page], isAdmin: Bo
object Status {
- implicit val reusability: Reusability[Status] =
- Reusability.derive[Status]
+ implicit val reusability: Reusability[Status] = Reusability.derive[Status]
def render(props: Status): VdomElement = {
def renderSbtTask(tasks: Vector[TaskId]): VdomElement = {
@@ -25,13 +23,12 @@ object Status {
div("No Task Running")
} else {
ul(
- tasks.zipWithIndex.map {
- case (TaskId(snippetId), j) =>
- li(key := snippetId.toString)(
- props.router.link(Page.fromSnippetId(snippetId))(
- s"Task $j"
- )
+ tasks.zipWithIndex.map { case (TaskId(snippetId), j) =>
+ li(key := snippetId.toString)(
+ props.router.link(Page.fromSnippetId(snippetId))(
+ s"Task $j"
)
+ )
}.toTagMod
)
}
@@ -51,31 +48,28 @@ object Status {
span(cls := "runner " + cssConfig)(label)
}
- val sbtRunnersStatus =
- props.state.sbtRunners match {
- case Some(sbtRunners) =>
- div(
- h1("Sbt Runners"),
- ul(
- sbtRunners.zipWithIndex.map {
- case (sbtRunner, i) =>
- li(key := i)(
- renderConfiguration(sbtRunner.config),
- renderSbtTask(sbtRunner.tasks)
- )
- }.toTagMod
- )
+ val sbtRunnersStatus = props.state.sbtRunners match {
+ case Some(sbtRunners) => div(
+ h1("Sbt Runners"),
+ ul(
+ sbtRunners.zipWithIndex.map { case (sbtRunner, i) =>
+ li(key := i)(
+ renderConfiguration(sbtRunner.config),
+ renderSbtTask(sbtRunner.tasks)
+ )
+ }.toTagMod
)
- case _ => div()
- }
+ )
+ case _ => div()
+ }
div(sbtRunnersStatus)
}
- private val component =
- ScalaComponent
- .builder[Status]("Status")
- .render_P(render)
- .configure(Reusability.shouldComponentUpdate)
- .build
+ private val component = ScalaComponent
+ .builder[Status]("Status")
+ .render_P(render)
+ .configure(Reusability.shouldComponentUpdate)
+ .build
+
}
diff --git a/client/src/main/scala/com.olegych.scastie.client/components/TargetSelector.scala b/client/src/main/scala/com.olegych.scastie.client/components/TargetSelector.scala
index bca1c389a..62dec25f5 100644
--- a/client/src/main/scala/com.olegych.scastie.client/components/TargetSelector.scala
+++ b/client/src/main/scala/com.olegych.scastie.client/components/TargetSelector.scala
@@ -2,7 +2,6 @@ package com.olegych.scastie.client.components
import com.olegych.scastie.api._
import japgolly.scalajs.react._
-
import vdom.all._
case class TargetSelector(scalaTarget: ScalaTarget, onChange: ScalaTarget ~=> Callback) {
@@ -28,27 +27,27 @@ object TargetSelector {
}
}
- val targetSelector =
- ScalaFnComponent
- .withHooks[TargetSelector]
- .render(props => {
- div(
- ul(cls := "target")(
- targetTypes.map { targetType =>
- val targetLabel = labelFor(targetType)
- li(
- input(
- `type` := "radio",
- id := targetLabel,
- value := targetLabel,
- name := "target",
- onChange --> props.onChange(targetType.defaultScalaTarget),
- checked := targetType == props.scalaTarget.targetType
- ),
- label(`for` := targetLabel, role := "button", cls := "radio", targetLabel)
- )
- }.toTagMod
- )
+ val targetSelector = ScalaFnComponent
+ .withHooks[TargetSelector]
+ .render(props => {
+ div(
+ ul(cls := "target")(
+ targetTypes.map { targetType =>
+ val targetLabel = labelFor(targetType)
+ li(
+ input(
+ `type` := "radio",
+ id := targetLabel,
+ value := targetLabel,
+ name := "target",
+ onChange --> props.onChange(targetType.defaultScalaTarget),
+ checked := targetType == props.scalaTarget.targetType
+ ),
+ label(`for` := targetLabel, role := "button", cls := "radio", targetLabel)
+ )
+ }.toTagMod
)
- })
+ )
+ })
+
}
diff --git a/client/src/main/scala/com.olegych.scastie.client/components/TopBar.scala b/client/src/main/scala/com.olegych.scastie.client/components/TopBar.scala
index 4b208c884..ab72316c9 100644
--- a/client/src/main/scala/com.olegych.scastie.client/components/TopBar.scala
+++ b/client/src/main/scala/com.olegych.scastie.client/components/TopBar.scala
@@ -3,10 +3,10 @@ package client
package components
import api.User
-
-import japgolly.scalajs.react._, vdom.all._, extra._
-
+import extra._
+import japgolly.scalajs.react._
import org.scalajs.dom
+import vdom.all._
final case class TopBar(view: StateSnapshot[View], user: Option[User], openLoginModal: Reusable[Callback]) {
@inline def render: VdomElement = TopBar.component(this)
@@ -14,54 +14,47 @@ final case class TopBar(view: StateSnapshot[View], user: Option[User], openLogin
object TopBar {
- implicit val reusability: Reusability[TopBar] =
- Reusability.derive[TopBar]
+ implicit val reusability: Reusability[TopBar] = Reusability.derive[TopBar]
private def render(props: TopBar): VdomElement = {
- def openInNewTab(link: String): Callback =
- Callback {
- dom.window.open(link, "_blank").focus()
- }
+ def openInNewTab(link: String): Callback = Callback {
+ dom.window.open(link, "_blank").focus()
+ }
- def feedback: Callback =
- openInNewTab("https://gitter.im/scalacenter/scastie")
+ def feedback: Callback = openInNewTab("https://gitter.im/scalacenter/scastie")
- def issue: Callback =
- openInNewTab("https://github.com/scalacenter/scastie/issues/new/choose")
+ def issue: Callback = openInNewTab("https://github.com/scalacenter/scastie/issues/new/choose")
val logoutUrl = "/logout"
- def logout: Callback =
- props.view.setState(View.Editor) >>
- Callback(dom.window.location.pathname = logoutUrl)
+ def logout: Callback = props.view.setState(View.Editor) >>
+ Callback(dom.window.location.pathname = logoutUrl)
- val profileButton =
- props.user match {
- case Some(user) =>
- li(
- cls := "btn dropdown",
- img(src := user.avatar_url + "&s=30", alt := "Your Github Avatar", cls := "avatar"),
- span(user.login),
- i(cls := "fa fa-caret-down"),
- ul(
- cls := "subactions",
- li(
- onClick --> props.view.setState(View.CodeSnippets),
- role := "link",
- title := "Go to your code snippets",
- cls := "btn",
- (cls := "selected").when(View.CodeSnippets == props.view.value)
- )(
- i(cls := "fa fa-code"),
- "Snippets"
- ),
- li(role := "link", onClick --> logout, cls := "btn", i(cls := "fa fa-sign-out"), "Logout")
- )
+ val profileButton = props.user match {
+ case Some(user) => li(
+ cls := "btn dropdown",
+ img(src := user.avatar_url + "&s=30", alt := "Your Github Avatar", cls := "avatar"),
+ span(user.login),
+ i(cls := "fa fa-caret-down"),
+ ul(
+ cls := "subactions",
+ li(
+ onClick --> props.view.setState(View.CodeSnippets),
+ role := "link",
+ title := "Go to your code snippets",
+ cls := "btn",
+ (cls := "selected").when(View.CodeSnippets == props.view.value)
+ )(
+ i(cls := "fa fa-code"),
+ "Snippets"
+ ),
+ li(role := "link", onClick --> logout, cls := "btn", i(cls := "fa fa-sign-out"), "Logout")
)
+ )
- case None =>
- li(role := "link", onClick --> props.openLoginModal, cls := "btn", i(cls := "fa fa-sign-in"), "Login")
- }
+ case None =>
+ li(role := "link", onClick --> props.openLoginModal, cls := "btn", i(cls := "fa fa-sign-in"), "Login")
+ }
nav(
cls := "topbar",
@@ -74,18 +67,22 @@ object TopBar {
i(cls := "fa fa-caret-down"),
ul(
cls := "subactions",
- li(onClick --> feedback,
- role := "link",
- title := "Open Gitter.im Chat to give us feedback",
- cls := "btn",
- i(cls := "fa fa-gitter"),
- span("Scastie's gitter")),
- li(onClick --> issue,
- role := "link",
- title := "Create new issue on GitHub",
- cls := "btn",
- i(cls := "fa fa-github"),
- span("Github issues"))
+ li(
+ onClick --> feedback,
+ role := "link",
+ title := "Open Gitter.im Chat to give us feedback",
+ cls := "btn",
+ i(cls := "fa fa-gitter"),
+ span("Scastie's gitter")
+ ),
+ li(
+ onClick --> issue,
+ role := "link",
+ title := "Create new issue on GitHub",
+ cls := "btn",
+ i(cls := "fa fa-github"),
+ span("Github issues")
+ )
)
),
profileButton
@@ -98,4 +95,5 @@ object TopBar {
.render_P(render)
.configure(Reusability.shouldComponentUpdate)
.build
+
}
diff --git a/client/src/main/scala/com.olegych.scastie.client/components/VersionSelector.scala b/client/src/main/scala/com.olegych.scastie.client/components/VersionSelector.scala
index cf931eb24..81587b31c 100644
--- a/client/src/main/scala/com.olegych.scastie.client/components/VersionSelector.scala
+++ b/client/src/main/scala/com.olegych.scastie.client/components/VersionSelector.scala
@@ -1,10 +1,9 @@
package com.olegych.scastie.client.components
import com.olegych.scastie.api._
+import com.olegych.scastie.buildinfo.BuildInfo
import japgolly.scalajs.react._
-
import vdom.all._
-import com.olegych.scastie.buildinfo.BuildInfo
case class VersionSelector(scalaTarget: ScalaTarget, onChange: ScalaTarget ~=> Callback) {
@inline def render: VdomElement = VersionSelector.versionSelectorHook(this)
@@ -12,66 +11,70 @@ case class VersionSelector(scalaTarget: ScalaTarget, onChange: ScalaTarget ~=> C
object VersionSelector {
- val versionSelectorHook =
- ScalaFnComponent
- .withHooks[VersionSelector]
- .render(props => {
- def versionSelectors(scalaVersion: String) =
- props.scalaTarget match {
- case d: ScalaTarget.Jvm => ScalaTarget.Jvm.apply(scalaVersion)
- case d: ScalaTarget.Typelevel => ScalaTarget.Typelevel.apply(scalaVersion)
- case d: ScalaTarget.Scala3 => ScalaTarget.Scala3.apply(scalaVersion)
- case js: ScalaTarget.Js => ScalaTarget.Js(scalaVersion, js.scalaJsVersion)
- case n: ScalaTarget.Native => ScalaTarget.Native(n.scalaNativeVersion, n.scalaVersion)
- }
+ val versionSelectorHook = ScalaFnComponent
+ .withHooks[VersionSelector]
+ .render(props => {
+ def versionSelectors(scalaVersion: String) = props.scalaTarget match {
+ case d: ScalaTarget.Jvm => ScalaTarget.Jvm.apply(scalaVersion)
+ case d: ScalaTarget.Typelevel => ScalaTarget.Typelevel.apply(scalaVersion)
+ case d: ScalaTarget.Scala3 => ScalaTarget.Scala3.apply(scalaVersion)
+ case js: ScalaTarget.Js => ScalaTarget.Js(scalaVersion, js.scalaJsVersion)
+ case n: ScalaTarget.Native => ScalaTarget.Native(n.scalaNativeVersion, n.scalaVersion)
+ }
- def renderRecommended3Versions(scalaVersion: String) = {
- if (scalaVersion == BuildInfo.stableLTS) s"$scalaVersion LTS"
- else if (scalaVersion == BuildInfo.stableNext) s"$scalaVersion Next"
- else scalaVersion
- }
+ def renderRecommended3Versions(scalaVersion: String) = {
+ if (scalaVersion == BuildInfo.stableLTS) s"$scalaVersion LTS"
+ else if (scalaVersion == BuildInfo.stableNext) s"$scalaVersion Next"
+ else scalaVersion
+ }
- ul(cls := "suggestedVersions")(
- ScalaVersions
- .suggestedScalaVersions(props.scalaTarget.targetType)
- .map { suggestedVersion =>
- li(
- input(
- `type` := "radio",
- id := s"scala-$suggestedVersion",
- value := suggestedVersion,
- name := "scalaV",
- onChange --> props.onChange(versionSelectors(suggestedVersion)),
- checked := props.scalaTarget.scalaVersion == suggestedVersion
- ),
- label(`for` := s"scala-$suggestedVersion", className := "radio", role := "button", renderRecommended3Versions(suggestedVersion))
+ ul(cls := "suggestedVersions")(
+ ScalaVersions
+ .suggestedScalaVersions(props.scalaTarget.targetType)
+ .map { suggestedVersion =>
+ li(
+ input(
+ `type` := "radio",
+ id := s"scala-$suggestedVersion",
+ value := suggestedVersion,
+ name := "scalaV",
+ onChange --> props.onChange(versionSelectors(suggestedVersion)),
+ checked := props.scalaTarget.scalaVersion == suggestedVersion
+ ),
+ label(
+ `for` := s"scala-$suggestedVersion",
+ className := "radio",
+ role := "button",
+ renderRecommended3Versions(suggestedVersion)
)
- }
- .toTagMod,
- li(
- label(
- div(cls := "select-wrapper"){
- val isRecommended = ScalaVersions
- .suggestedScalaVersions(props.scalaTarget.targetType)
- .contains(props.scalaTarget.scalaVersion)
-
- select(
- name := "scalaVersion",
- onChange ==> { (e: ReactEventFromInput) =>
- props.onChange(versionSelectors(e.target.value))
- },
- value := {if (isRecommended) "Other" else props.scalaTarget.scalaVersion},
- TagMod.when(!isRecommended)(className := "selected-option")
- )(
- ScalaVersions
- .allVersions(props.scalaTarget.targetType)
- .map(version => option(version))
- .prepended(option("Other")(hidden := true, disabled := true))
- .toTagMod
- )
- }
)
+ }
+ .toTagMod,
+ li(
+ label(
+ div(cls := "select-wrapper") {
+ val isRecommended = ScalaVersions
+ .suggestedScalaVersions(props.scalaTarget.targetType)
+ .contains(props.scalaTarget.scalaVersion)
+
+ select(
+ name := "scalaVersion",
+ onChange ==> { (e: ReactEventFromInput) =>
+ props.onChange(versionSelectors(e.target.value))
+ },
+ value := { if (isRecommended) "Other" else props.scalaTarget.scalaVersion },
+ TagMod.when(!isRecommended)(className := "selected-option")
+ )(
+ ScalaVersions
+ .allVersions(props.scalaTarget.targetType)
+ .map(version => option(version))
+ .prepended(option("Other")(hidden := true, disabled := true))
+ .toTagMod
+ )
+ }
)
)
- })
+ )
+ })
+
}
diff --git a/client/src/main/scala/com.olegych.scastie.client/components/ViewToggleButton.scala b/client/src/main/scala/com.olegych.scastie.client/components/ViewToggleButton.scala
index 79791a78d..c00ede8a3 100644
--- a/client/src/main/scala/com.olegych.scastie.client/components/ViewToggleButton.scala
+++ b/client/src/main/scala/com.olegych.scastie.client/components/ViewToggleButton.scala
@@ -2,38 +2,41 @@ package com.olegych.scastie
package client
package components
-import japgolly.scalajs.react._, vdom.all._, extra._
+import extra._
+import japgolly.scalajs.react._
+import vdom.all._
-final case class ViewToggleButton(currentView: StateSnapshot[View],
- forView: View,
- buttonTitle: String,
- faIcon: String,
- onClick: Reusable[Callback]) {
+final case class ViewToggleButton(
+ currentView: StateSnapshot[View],
+ forView: View,
+ buttonTitle: String,
+ faIcon: String,
+ onClick: Reusable[Callback]
+) {
@inline def render: VdomElement = ViewToggleButton.component(this)
}
object ViewToggleButton {
- implicit val reusability: Reusability[ViewToggleButton] =
- Reusability.derive[ViewToggleButton]
+ implicit val reusability: Reusability[ViewToggleButton] = Reusability.derive[ViewToggleButton]
private def render(props: ViewToggleButton): VdomElement = {
li(
onClick --> (props.currentView.setState(props.forView) >> props.onClick),
- role := "button",
+ role := "button",
title := props.buttonTitle,
- (cls := "selected").when(props.currentView.value == props.forView),
- cls := "btn"
+ (cls := "selected").when(props.currentView.value == props.forView),
+ cls := "btn"
)(
i(cls := props.faIcon, cls := "fa"),
span(props.buttonTitle)
)
}
- private val component =
- ScalaComponent
- .builder[ViewToggleButton]("ViewToggleButton")
- .render_P(render)
- .configure(Reusability.shouldComponentUpdate)
- .build
+ private val component = ScalaComponent
+ .builder[ViewToggleButton]("ViewToggleButton")
+ .render_P(render)
+ .configure(Reusability.shouldComponentUpdate)
+ .build
+
}
diff --git a/client/src/main/scala/com.olegych.scastie.client/components/WorksheetButton.scala b/client/src/main/scala/com.olegych.scastie.client/components/WorksheetButton.scala
index e8518539a..d41194455 100644
--- a/client/src/main/scala/com.olegych.scastie.client/components/WorksheetButton.scala
+++ b/client/src/main/scala/com.olegych.scastie.client/components/WorksheetButton.scala
@@ -3,32 +3,27 @@ package client
package components
import japgolly.scalajs.react._
-
import vdom.all._
final case class WorksheetButton(
- hasWorksheetMode: Boolean,
- isWorksheetMode: Boolean,
- toggleWorksheetMode: Reusable[Callback],
- view: View
+ hasWorksheetMode: Boolean,
+ isWorksheetMode: Boolean,
+ toggleWorksheetMode: Reusable[Callback],
+ view: View
) {
@inline def render: VdomElement = WorksheetButton.component(this)
}
object WorksheetButton {
- implicit val reusability: Reusability[WorksheetButton] =
- Reusability.derive[WorksheetButton]
+ implicit val reusability: Reusability[WorksheetButton] = Reusability.derive[WorksheetButton]
private def render(props: WorksheetButton): VdomElement = {
val isWorksheetModeSelected =
if (props.isWorksheetMode)
- if (props.view != View.Editor)
- TagMod(cls := "enabled alpha")
- else
- TagMod(cls := "enabled")
- else
- EmptyVdom
+ if (props.view != View.Editor) TagMod(cls := "enabled alpha")
+ else TagMod(cls := "enabled")
+ else EmptyVdom
val isWorksheetModeToggleLabel =
if (props.isWorksheetMode) "OFF"
@@ -40,7 +35,7 @@ object WorksheetButton {
else "This configuration does not support Worksheet mode"),
isWorksheetModeSelected,
role := "button",
- cls := "btn editor",
+ cls := "btn editor",
onClick --> props.toggleWorksheetMode
)(
i(cls := "fa fa-calendar"),
@@ -49,10 +44,10 @@ object WorksheetButton {
)
}
- private val component =
- ScalaComponent
- .builder[WorksheetButton]("WorksheetButton")
- .render_P(render)
- .configure(Reusability.shouldComponentUpdate)
- .build
+ private val component = ScalaComponent
+ .builder[WorksheetButton]("WorksheetButton")
+ .render_P(render)
+ .configure(Reusability.shouldComponentUpdate)
+ .build
+
}
diff --git a/client/src/main/scala/com.olegych.scastie.client/components/editor/CodeEditor.scala b/client/src/main/scala/com.olegych.scastie.client/components/editor/CodeEditor.scala
index 939b8dbad..8854a1cdf 100644
--- a/client/src/main/scala/com.olegych.scastie.client/components/editor/CodeEditor.scala
+++ b/client/src/main/scala/com.olegych.scastie.client/components/editor/CodeEditor.scala
@@ -1,12 +1,15 @@
package com.olegych.scastie.client.components.editor
import com.olegych.scastie.api
-import com.olegych.scastie.client.HTMLFormatter
import com.olegych.scastie.client._
+import com.olegych.scastie.client.HTMLFormatter
+import hooks.Hooks.UseStateF
import japgolly.scalajs.react._
+import js.JSConverters._
import org.scalajs.dom
import org.scalajs.dom.Element
import org.scalajs.dom.HTMLElement
+import scalajs.js
import typings.codemirrorAutocomplete.mod._
import typings.codemirrorCommands.mod._
import typings.codemirrorLanguage.mod._
@@ -15,93 +18,96 @@ import typings.codemirrorLint.mod._
import typings.codemirrorSearch.mod._
import typings.codemirrorState.mod._
import typings.codemirrorView.mod._
-
-import scalajs.js
import vdom.all._
import JsUtils._
-import hooks.Hooks.UseStateF
-import js.JSConverters._
-final case class CodeEditor(visible: Boolean,
- isDarkTheme: Boolean,
- isPresentationMode: Boolean,
- isWorksheetMode: Boolean,
- isEmbedded: Boolean,
- showLineNumbers: Boolean,
- value: String,
- attachedDoms: Map[String, HTMLElement],
- instrumentations: Set[api.Instrumentation],
- compilationInfos: Set[api.Problem],
- runtimeError: Option[api.RuntimeError],
- saveOrUpdate: Reusable[Callback],
- clear: Reusable[Callback],
- openNewSnippetModal: Reusable[Callback],
- toggleHelp: Reusable[Callback],
- toggleConsole: Reusable[Callback],
- toggleLineNumbers: Reusable[Callback],
- togglePresentationMode: Reusable[Callback],
- formatCode: Reusable[Callback],
- codeChange: String ~=> Callback,
- target: api.ScalaTarget,
- metalsStatus: MetalsStatus,
- setMetalsStatus: MetalsStatus ~=> Callback,
- dependencies: Set[api.ScalaDependency])
- extends Editor {
+final case class CodeEditor(
+ visible: Boolean,
+ isDarkTheme: Boolean,
+ isPresentationMode: Boolean,
+ isWorksheetMode: Boolean,
+ isEmbedded: Boolean,
+ showLineNumbers: Boolean,
+ value: String,
+ attachedDoms: Map[String, HTMLElement],
+ instrumentations: Set[api.Instrumentation],
+ compilationInfos: Set[api.Problem],
+ runtimeError: Option[api.RuntimeError],
+ saveOrUpdate: Reusable[Callback],
+ clear: Reusable[Callback],
+ openNewSnippetModal: Reusable[Callback],
+ toggleHelp: Reusable[Callback],
+ toggleConsole: Reusable[Callback],
+ toggleLineNumbers: Reusable[Callback],
+ togglePresentationMode: Reusable[Callback],
+ formatCode: Reusable[Callback],
+ codeChange: String ~=> Callback,
+ target: api.ScalaTarget,
+ metalsStatus: MetalsStatus,
+ setMetalsStatus: MetalsStatus ~=> Callback,
+ dependencies: Set[api.ScalaDependency]
+) extends Editor {
@inline def render: VdomElement = CodeEditor.hooksComponent(this)
}
object CodeEditor {
- private def init(props: CodeEditor, ref: Ref.Simple[Element], editorView: UseStateF[CallbackTo, EditorView]): Callback =
- ref.foreachCB(divRef => {
-
- val syntaxHighlighting = new SyntaxHighlightingPlugin(editorView)
- val extensions = js.Array[Any](
- Editor.editorTheme.of(props.codemirrorTheme),
- lineNumbers(),
- highlightSpecialChars(),
- history(),
- drawSelection(),
- dropCursor(),
- EditorState.allowMultipleSelections.of(true),
- indentOnInput(),
- bracketMatching(),
- closeBrackets(),
- rectangularSelection(),
- crosshairCursor(),
- highlightSelectionMatches(),
- Editor.indentationMarkersExtension,
- keymap.of(closeBracketsKeymap ++ defaultKeymap ++ historyKeymap ++ foldKeymap ++ completionKeymap ++ lintKeymap ++ searchKeymap),
- StateField
- .define(StateFieldSpec[Set[api.Instrumentation]](_ => props.instrumentations, (value, _) => value))
- .extension,
- DecorationProvider(props),
- EditorState.tabSize.of(2),
- Prec.highest(EditorKeymaps.keymapping(props)),
- InteractiveProvider.interactive.of(InteractiveProvider(props).extension),
- SyntaxHighlightingTheme.highlightingTheme,
- lintGutter(),
- OnChangeHandler(props.codeChange),
- syntaxHighlighting.syntaxHighlightingExtension.of(syntaxHighlighting.fallbackExtension),
- )
+ private def init(
+ props: CodeEditor,
+ ref: Ref.Simple[Element],
+ editorView: UseStateF[CallbackTo, EditorView]
+ ): Callback = ref.foreachCB(divRef => {
+
+ val syntaxHighlighting = new SyntaxHighlightingPlugin(editorView)
+ val extensions = js.Array[Any](
+ Editor.editorTheme.of(props.codemirrorTheme),
+ lineNumbers(),
+ highlightSpecialChars(),
+ history(),
+ drawSelection(),
+ dropCursor(),
+ EditorState.allowMultipleSelections.of(true),
+ indentOnInput(),
+ bracketMatching(),
+ closeBrackets(),
+ rectangularSelection(),
+ crosshairCursor(),
+ highlightSelectionMatches(),
+ Editor.indentationMarkersExtension,
+ keymap.of(
+ closeBracketsKeymap ++ defaultKeymap ++ historyKeymap ++ foldKeymap ++ completionKeymap ++ lintKeymap ++ searchKeymap
+ ),
+ StateField
+ .define(StateFieldSpec[Set[api.Instrumentation]](_ => props.instrumentations, (value, _) => value))
+ .extension,
+ DecorationProvider(props),
+ EditorState.tabSize.of(2),
+ Prec.highest(EditorKeymaps.keymapping(props)),
+ InteractiveProvider.interactive.of(InteractiveProvider(props).extension),
+ SyntaxHighlightingTheme.highlightingTheme,
+ lintGutter(),
+ OnChangeHandler(props.codeChange),
+ syntaxHighlighting.syntaxHighlightingExtension.of(syntaxHighlighting.fallbackExtension)
+ )
- val editorStateConfig = EditorStateConfig()
- .setExtensions(extensions)
- .setDoc(props.value)
+ val editorStateConfig = EditorStateConfig()
+ .setExtensions(extensions)
+ .setDoc(props.value)
- val editor = new EditorView(EditorViewConfig()
+ val editor = new EditorView(
+ EditorViewConfig()
.setState(EditorState.create(editorStateConfig))
.setParent(divRef)
- )
+ )
- editorView.setState(editor)
- })
+ editorView.setState(editor)
+ })
private def getDecorations(props: CodeEditor, doc: Text): js.Array[Diagnostic] = {
val errors = props.compilationInfos
.filter(prob => prob.line.isDefined && prob.line.get <= doc.lines)
.map(problem => {
- val line = problem.line.get max 1
+ val line = problem.line.get max 1
val lineInfo = doc.line(line)
Diagnostic(lineInfo.from, HTMLFormatter.format(problem.message), parseSeverity(problem.severity), lineInfo.to)
@@ -114,32 +120,38 @@ object CodeEditor {
})
val runtimeErrors = props.runtimeError.map(runtimeError => {
- val line = runtimeError.line.getOrElse(1).min(doc.lines.toInt)
+ val line = runtimeError.line.getOrElse(1).min(doc.lines.toInt)
val lineInfo = doc.line(line)
- val msg = if (runtimeError.fullStack.nonEmpty) runtimeError.fullStack else runtimeError.message
+ val msg = if (runtimeError.fullStack.nonEmpty) runtimeError.fullStack else runtimeError.message
Diagnostic(lineInfo.from, HTMLFormatter.format(msg), codemirrorLintStrings.error, lineInfo.to)
})
(errors ++ runtimeErrors).toJSArray
}
- private def updateDiagnostics(editorView: UseStateF[CallbackTo, EditorView], prevProps: Option[CodeEditor], props: CodeEditor): Callback = {
+ private def updateDiagnostics(
+ editorView: UseStateF[CallbackTo, EditorView],
+ prevProps: Option[CodeEditor],
+ props: CodeEditor
+ ): Callback = {
Callback {
- editorView.value.dispatch(setDiagnostics(editorView.value.state, getDecorations(props, editorView.value.state.doc)))
+ editorView.value.dispatch(
+ setDiagnostics(editorView.value.state, getDecorations(props, editorView.value.state.doc))
+ )
}.when_(
prevProps.isDefined &&
props.value == editorView.value.state.doc.toString() && (
- prevProps.get.compilationInfos != props.compilationInfos ||
- prevProps.get.runtimeError != props.runtimeError
- )
+ prevProps.get.compilationInfos != props.compilationInfos ||
+ prevProps.get.runtimeError != props.runtimeError
+ )
)
}
private def updateComponent(
- props: CodeEditor,
- ref: Ref.Simple[Element],
- prevProps: Option[CodeEditor],
- editorView: UseStateF[CallbackTo, EditorView]
+ props: CodeEditor,
+ ref: Ref.Simple[Element],
+ prevProps: Option[CodeEditor],
+ editorView: UseStateF[CallbackTo, EditorView]
): Callback = {
Editor.updateCode(editorView, props) >>
Editor.updateTheme(ref, prevProps, props, editorView) >>
@@ -148,16 +160,15 @@ object CodeEditor {
InteractiveProvider.reloadMetalsConfiguration(editorView, prevProps, props)
}
- val hooksComponent =
- ScalaFnComponent
- .withHooks[CodeEditor]
- .useRef(Ref[Element])
- .useRef[Option[CodeEditor]](None)
- .useState(new EditorView())
- .useEffectOnMountBy((props, ref, prevProps, editorView) => init(props, ref.value, editorView))
- .useEffectBy(
- (props, ref, prevProps, editorView) => updateComponent(props, ref.value, prevProps.value, editorView) >> prevProps.set(Some(props))
- )
- .render((_, ref, _, _) => Editor.render(ref.value))
+ val hooksComponent = ScalaFnComponent
+ .withHooks[CodeEditor]
+ .useRef(Ref[Element])
+ .useRef[Option[CodeEditor]](None)
+ .useState(new EditorView())
+ .useEffectOnMountBy((props, ref, prevProps, editorView) => init(props, ref.value, editorView))
+ .useEffectBy((props, ref, prevProps, editorView) =>
+ updateComponent(props, ref.value, prevProps.value, editorView) >> prevProps.set(Some(props))
+ )
+ .render((_, ref, _, _) => Editor.render(ref.value))
}
diff --git a/client/src/main/scala/com.olegych.scastie.client/components/editor/DebouncingCapabilities.scala b/client/src/main/scala/com.olegych.scastie.client/components/editor/DebouncingCapabilities.scala
index 7a73c807a..a0079a1fd 100644
--- a/client/src/main/scala/com.olegych.scastie.client/components/editor/DebouncingCapabilities.scala
+++ b/client/src/main/scala/com.olegych.scastie.client/components/editor/DebouncingCapabilities.scala
@@ -1,15 +1,13 @@
package com.olegych.scastie.client.components.editor
-import typings.codemirrorState.mod._
-import typings.codemirrorView.mod._
-
import scala.concurrent.duration._
import scala.scalajs.js.timers._
import scalajs.js
+import typings.codemirrorState.mod._
+import typings.codemirrorView.mod._
import EditorTextOps._
-
trait DebouncingCapabilities {
type OnChange = (String, EditorView) => Unit
@@ -17,13 +15,11 @@ trait DebouncingCapabilities {
FacetConfig[OnChange, OnChange]().setCombine(input => over(input.toSeq))
}
- private def debounce(fn: OnChange): OnChange = {
+ private def debounce(fn: OnChange): OnChange = {
var timeout: js.UndefOr[js.timers.SetTimeoutHandle] = js.undefined
(code: String, view: EditorView) => {
- val tokenLength = view
- .lineBeforeCursor
- .reverseIterator
+ val tokenLength = view.lineBeforeCursor.reverseIterator
.takeWhile(c => !c.isWhitespace || c == '.')
.length
@@ -38,8 +34,8 @@ trait DebouncingCapabilities {
}
}
- private def over(functions: Seq[OnChange]): OnChange = {
- (code: String, view: EditorView) => functions.foreach(f => f(code, view))
+ private def over(functions: Seq[OnChange]): OnChange = { (code: String, view: EditorView) =>
+ functions.foreach(f => f(code, view))
}
protected def onChangeCallback(onChange: OnChange): Extension = {
@@ -49,11 +45,12 @@ trait DebouncingCapabilities {
onChangeFacet.of(debouncedOnChange),
EditorView.updateListener.of(viewUpdate => {
if (viewUpdate.docChanged) {
- val content = viewUpdate.state.sliceDoc()
+ val content = viewUpdate.state.sliceDoc()
val onChange = viewUpdate.state.facet[OnChange](onChangeFacet.asInstanceOf[Facet[Any, OnChange]])
onChange(content, viewUpdate.view)
}
})
)
}
+
}
diff --git a/client/src/main/scala/com.olegych.scastie.client/components/editor/DecorationProvider.scala b/client/src/main/scala/com.olegych.scastie.client/components/editor/DecorationProvider.scala
index 19e4dee0e..00b0f95de 100644
--- a/client/src/main/scala/com.olegych.scastie.client/components/editor/DecorationProvider.scala
+++ b/client/src/main/scala/com.olegych.scastie.client/components/editor/DecorationProvider.scala
@@ -1,24 +1,24 @@
package com.olegych.scastie.client.components.editor
+import scala.collection.mutable.ListBuffer
+
import com.olegych.scastie.api
import com.olegych.scastie.api.AttachedDom
import com.olegych.scastie.api.Html
import com.olegych.scastie.api.Value
+import hooks.Hooks.UseStateF
import japgolly.scalajs.react._
+import js.JSConverters._
import org.scalajs.dom
import org.scalajs.dom.HTMLElement
+import scalajs.js
import typings.codemirrorState.mod._
import typings.codemirrorView.mod._
-import scala.collection.mutable.ListBuffer
-
-import scalajs.js
-import hooks.Hooks.UseStateF
-import js.JSConverters._
-
object DecorationProvider {
final class AttachedDomDecoration(uuid: String, attachedDoms: Map[String, HTMLElement]) extends WidgetType {
+
override def toDOM(view: EditorView): HTMLElement = {
val wrap = dom.document.createElement("div")
wrap.setAttribute("aria-hidden", "true")
@@ -26,9 +26,11 @@ object DecorationProvider {
attachedDoms.get(uuid).map(wrap.append(_))
wrap.domAsHtml
}
+
}
final class TypeDecoration(value: String, typeName: String) extends WidgetType {
+
override def toDOM(view: EditorView): HTMLElement = {
val wrap = dom.document.createElement("span")
wrap.setAttribute("aria-hidden", "true")
@@ -45,9 +47,11 @@ object DecorationProvider {
wrap.append(textBody)
wrap.domAsHtml
}
+
}
final class HTMLDecoration(html: String) extends WidgetType {
+
override def toDOM(view: EditorView): HTMLElement = {
val wrap = dom.document.createElement("pre")
wrap.setAttribute("aria-hidden", "true")
@@ -55,9 +59,14 @@ object DecorationProvider {
wrap.innerHTML = html
wrap.domAsHtml
}
+
}
- private def createDecorations(instrumentations: Set[api.Instrumentation], attachedDoms: Map[String, HTMLElement], maxPosititon: Int): DecorationSet = {
+ private def createDecorations(
+ instrumentations: Set[api.Instrumentation],
+ attachedDoms: Map[String, HTMLElement],
+ maxPosititon: Int
+ ): DecorationSet = {
val deco = instrumentations
.filter(_.position.end < maxPosititon)
.map { instrumentation =>
@@ -77,11 +86,11 @@ object DecorationProvider {
Decoration.set(x, true)
}
- private val addTypeDecorations = StateEffect.define[DecorationSet]()
+ private val addTypeDecorations = StateEffect.define[DecorationSet]()
private val filterTypeDecorations = StateEffect.define[DecorationSet]()
private def updateDecorationPositions(previousValue: DecorationSet, transaction: Transaction): DecorationSet = {
- val newNewlines: ListBuffer[Int] = ListBuffer.empty
+ val newNewlines: ListBuffer[Int] = ListBuffer.empty
val decorationsToReAdd: ListBuffer[Range[Decoration]] = ListBuffer.empty
transaction.changes.iterChanges((_, _, fromB, toB, _) => {
transaction.newDoc.sliceString(fromB, toB).lastOption.foreach {
@@ -103,12 +112,10 @@ object DecorationProvider {
}.asInstanceOf[RangeSetUpdate[DecorationSet]])
.map(transaction.changes)
- if (decorationsToReAdd.isEmpty)
- newValues
- else
- newValues.update(new js.Object {
- var add = decorationsToReAdd.toJSArray
- }.asInstanceOf[RangeSetUpdate[DecorationSet]])
+ if (decorationsToReAdd.isEmpty) newValues
+ else newValues.update(new js.Object {
+ var add = decorationsToReAdd.toJSArray
+ }.asInstanceOf[RangeSetUpdate[DecorationSet]])
}
private def updateState(previousValue: DecorationSet, transaction: Transaction): DecorationSet = {
@@ -121,8 +128,7 @@ object DecorationProvider {
val decorationSet = stateEffect.value.asInstanceOf[DecorationSet]
if (decorationSet.size > 0) decorationSet else Decoration.none
}
- case _ =>
- updateDecorationPositions(previousValue, transaction)
+ case _ => updateDecorationPositions(previousValue, transaction)
}
}
@@ -130,37 +136,36 @@ object DecorationProvider {
!ignoredRanges.contains(from)
}
- private def stateFieldSpec(props: CodeEditor) =
- StateFieldSpec[DecorationSet](
- create = _ => createDecorations(props.instrumentations, props.attachedDoms, props.value.length),
- update = updateState,
- ).setProvide(v => EditorView.decorations.from(v))
+ private def stateFieldSpec(props: CodeEditor) = StateFieldSpec[DecorationSet](
+ create = _ => createDecorations(props.instrumentations, props.attachedDoms, props.value.length),
+ update = updateState
+ ).setProvide(v => EditorView.decorations.from(v))
def updateDecorations(
- editorView: UseStateF[CallbackTo, EditorView],
- prevProps: Option[CodeEditor],
- props: CodeEditor
- ): Callback =
- Callback {
- val decorations = createDecorations(props.instrumentations, props.attachedDoms, editorView.value.state.doc.length.toInt + 1)
- val addTypesEffect = addTypeDecorations.of(decorations)
- val changes = new js.Object {
- var desc = new js.Object {
- var length = prevProps.map(_.value.length).getOrElse(0)
- var newLength = props.value.length
- var empty = newLength == length
- }.asInstanceOf[ChangeDesc]
- }.asInstanceOf[ChangeSpec]
-
- editorView.value.dispatch(
- TransactionSpec()
- .setChanges(changes)
- .setEffects(addTypesEffect.asInstanceOf[StateEffect[Any]])
- )
- }.when_(
- prevProps.isDefined &&
- (props.instrumentations != prevProps.get.instrumentations)
+ editorView: UseStateF[CallbackTo, EditorView],
+ prevProps: Option[CodeEditor],
+ props: CodeEditor
+ ): Callback = Callback {
+ val decorations =
+ createDecorations(props.instrumentations, props.attachedDoms, editorView.value.state.doc.length.toInt + 1)
+ val addTypesEffect = addTypeDecorations.of(decorations)
+ val changes = new js.Object {
+ var desc = new js.Object {
+ var length = prevProps.map(_.value.length).getOrElse(0)
+ var newLength = props.value.length
+ var empty = newLength == length
+ }.asInstanceOf[ChangeDesc]
+ }.asInstanceOf[ChangeSpec]
+
+ editorView.value.dispatch(
+ TransactionSpec()
+ .setChanges(changes)
+ .setEffects(addTypesEffect.asInstanceOf[StateEffect[Any]])
)
+ }.when_(
+ prevProps.isDefined &&
+ (props.instrumentations != prevProps.get.instrumentations)
+ )
def apply(props: CodeEditor): Extension = StateField.define(stateFieldSpec(props)).extension
}
diff --git a/client/src/main/scala/com.olegych.scastie.client/components/editor/Editor.scala b/client/src/main/scala/com.olegych.scastie.client/components/editor/Editor.scala
index b4fb7b472..3b6eed12a 100644
--- a/client/src/main/scala/com.olegych.scastie.client/components/editor/Editor.scala
+++ b/client/src/main/scala/com.olegych.scastie.client/components/editor/Editor.scala
@@ -3,13 +3,12 @@ package com.olegych.scastie.client.components.editor
import japgolly.scalajs.react._
import org.scalablytyped.runtime.StringDictionary
import org.scalajs.dom.Element
+import scalajs.js
import typings.codemirrorState.mod._
import typings.codemirrorView.anon
import typings.codemirrorView.mod._
import typings.replitCodemirrorIndentationMarkers.anon.ActiveDark
import typings.replitCodemirrorIndentationMarkers.mod._
-
-import scalajs.js
import vdom.all._
trait Editor {
@@ -35,20 +34,24 @@ object Editor {
def render(ref: Ref.Simple[Element]): VdomElement =
div(cls := "editor-wrapper cm-s-solarized cm-s-light").withRef(ref)
- def updateTheme(ref: Ref.Simple[Element], prevProps: Option[Editor], props: Editor, editorView: hooks.Hooks.UseStateF[CallbackTo, EditorView]): Callback =
- ref
- .foreach(ref => {
- val cssTheme = if (props.isDarkTheme) "dark" else "light"
- editorView.value.dispatch(TransactionSpec().setEffects(editorTheme.reconfigure(props.codemirrorTheme)))
- ref.setAttribute("class", s"editor-wrapper cm-s-solarized cm-s-$cssTheme")
- })
- .when_(prevProps.map(_.isDarkTheme != props.isDarkTheme).getOrElse(true))
+ def updateTheme(
+ ref: Ref.Simple[Element],
+ prevProps: Option[Editor],
+ props: Editor,
+ editorView: hooks.Hooks.UseStateF[CallbackTo, EditorView]
+ ): Callback = ref
+ .foreach(ref => {
+ val cssTheme = if (props.isDarkTheme) "dark" else "light"
+ editorView.value.dispatch(TransactionSpec().setEffects(editorTheme.reconfigure(props.codemirrorTheme)))
+ ref.setAttribute("class", s"editor-wrapper cm-s-solarized cm-s-$cssTheme")
+ })
+ .when_(prevProps.map(_.isDarkTheme != props.isDarkTheme).getOrElse(true))
def updateCode(editorView: Hooks.UseStateF[CallbackTo, EditorView], newState: Editor): Callback = {
Callback {
editorView.value.dispatch(TransactionSpec().setChanges(new js.Object {
- var from = 0
- var to = editorView.value.state.doc.length
+ var from = 0
+ var to = editorView.value.state.doc.length
var insert = newState.value
}.asInstanceOf[ChangeSpec]))
}.when_(editorView.value.state.doc.toString() != newState.value)
diff --git a/client/src/main/scala/com.olegych.scastie.client/components/editor/EditorKeymaps.scala b/client/src/main/scala/com.olegych.scastie.client/components/editor/EditorKeymaps.scala
index 2c0e005b4..fa4904112 100644
--- a/client/src/main/scala/com.olegych.scastie.client/components/editor/EditorKeymaps.scala
+++ b/client/src/main/scala/com.olegych.scastie.client/components/editor/EditorKeymaps.scala
@@ -1,16 +1,15 @@
package com.olegych.scastie.client.components.editor
+import com.olegych.scastie.client
import org.scalajs.dom
-import typings.codemirrorState.anon
-import typings.codemirrorView.mod.EditorView
-import typings.codemirrorView.mod.{KeyBinding => JSKeyBinding}
-import typings.codemirrorCommands.mod._
+import scalajs.js
import typings.codemirrorAutocomplete.mod.acceptCompletion
+import typings.codemirrorCommands.mod._
+import typings.codemirrorState.anon
import typings.codemirrorState.mod._
-import com.olegych.scastie.client
-
-import scalajs.js
import typings.codemirrorState.mod.TransactionSpec
+import typings.codemirrorView.mod.{KeyBinding => JSKeyBinding}
+import typings.codemirrorView.mod.EditorView
object EditorKeymaps {
@@ -23,31 +22,31 @@ object EditorKeymaps {
}
}
- val saveOrUpdate = new Key("Ctrl-Enter", "Meta-Enter")
- val saveOrUpdateAlt = new Key("Ctrl-s", "Meta-s")
+ val saveOrUpdate = new Key("Ctrl-Enter", "Meta-Enter")
+ val saveOrUpdateAlt = new Key("Ctrl-s", "Meta-s")
val openNewSnippetModal = new Key("Ctrl-m", "Meta-m")
- val clear = new Key("Escape")
- val clearAlt = new Key("F1")
- val console = new Key("F3")
- val help = new Key("F5")
- val format = new Key("F6")
- val presentation = new Key("F8")
+ val clear = new Key("Escape")
+ val clearAlt = new Key("F1")
+ val console = new Key("F3")
+ val help = new Key("F5")
+ val format = new Key("F6")
+ val presentation = new Key("F8")
- def keymapping(e: CodeEditor) =
- typings.codemirrorView.mod.keymap.of(
- js.Array(
- KeyBinding.tabKeybind,
- KeyBinding(_ => e.saveOrUpdate.runNow(), saveOrUpdate, true),
- KeyBinding(_ => e.saveOrUpdate.runNow(), saveOrUpdateAlt, true),
- KeyBinding(_ => e.openNewSnippetModal.runNow(), openNewSnippetModal, true),
- KeyBinding(_ => e.clear.runNow(), clear, true),
- KeyBinding(_ => e.clear.runNow(), clearAlt, true),
- KeyBinding(_ => e.toggleHelp.runNow(), help, true),
- KeyBinding(_ => e.toggleConsole.runNow(), console, true),
- KeyBinding(_ => e.formatCode.runNow(), format, true),
- KeyBinding(_ => presentationMode(e), presentation, true),
- )
+ def keymapping(e: CodeEditor) = typings.codemirrorView.mod.keymap.of(
+ js.Array(
+ KeyBinding.tabKeybind,
+ KeyBinding(_ => e.saveOrUpdate.runNow(), saveOrUpdate, true),
+ KeyBinding(_ => e.saveOrUpdate.runNow(), saveOrUpdateAlt, true),
+ KeyBinding(_ => e.openNewSnippetModal.runNow(), openNewSnippetModal, true),
+ KeyBinding(_ => e.clear.runNow(), clear, true),
+ KeyBinding(_ => e.clear.runNow(), clearAlt, true),
+ KeyBinding(_ => e.toggleHelp.runNow(), help, true),
+ KeyBinding(_ => e.toggleConsole.runNow(), console, true),
+ KeyBinding(_ => e.formatCode.runNow(), format, true),
+ KeyBinding(_ => presentationMode(e), presentation, true)
)
+ )
+
}
case class Key(default: String, linux: String, mac: String, win: String) {
@@ -63,22 +62,25 @@ case class Key(default: String, linux: String, mac: String, win: String) {
val macAdjusted = if (client.isMac) mac.replace("Meta", "Cmd") else default
macAdjusted.replace("Escape", "Esc")
}
+
}
object KeyBinding {
+
val tabKeybind: JSKeyBinding = {
val key = new Key("Tab")
JSKeyBinding()
.setRun(view =>
- if (!acceptCompletion(view)) {
- view.dispatch(
- TransactionSpec()
- .setChanges(js.Dynamic.literal(from = view.state.selection.main.head, insert = " ").asInstanceOf[ChangeSpec])
- .setSelection(EditorSelection.single(view.state.selection.main.head + 2))
- )
- true
- }
- else false
+ if (!acceptCompletion(view)) {
+ view.dispatch(
+ TransactionSpec()
+ .setChanges(
+ js.Dynamic.literal(from = view.state.selection.main.head, insert = " ").asInstanceOf[ChangeSpec]
+ )
+ .setSelection(EditorSelection.single(view.state.selection.main.head + 2))
+ )
+ true
+ } else false
)
.setKey(key.default)
.setLinux(key.linux)
@@ -96,4 +98,5 @@ object KeyBinding {
.setWin(key.win)
.setPreventDefault(preventDefault)
}
+
}
diff --git a/client/src/main/scala/com.olegych.scastie.client/components/editor/EditorTextOps.scala b/client/src/main/scala/com.olegych.scastie.client/components/editor/EditorTextOps.scala
index e87d2a36e..8d715aa10 100644
--- a/client/src/main/scala/com.olegych.scastie.client/components/editor/EditorTextOps.scala
+++ b/client/src/main/scala/com.olegych.scastie.client/components/editor/EditorTextOps.scala
@@ -2,14 +2,14 @@ package com.olegych.scastie.client.components.editor
import typings.codemirrorView.mod._
-
object EditorTextOps {
val regex = """\.\w*|\w+""".r
implicit class EditorTextOpsOps(view: EditorView) {
+
def lineBeforeCursor: String = {
- val pos = view.state.selection.main.from
- val line = view.state.doc.lineAt(pos)
+ val pos = view.state.selection.main.from
+ val line = view.state.doc.lineAt(pos)
val start = Math.max(line.from, pos - 250)
line.text.slice(start.toInt - line.from.toInt, pos.toInt - line.from.toInt)
}
@@ -20,4 +20,5 @@ object EditorTextOps {
}
}
+
}
diff --git a/client/src/main/scala/com.olegych.scastie.client/components/editor/InteractiveProvider.scala b/client/src/main/scala/com.olegych.scastie.client/components/editor/InteractiveProvider.scala
index a035576c6..b541f6951 100644
--- a/client/src/main/scala/com.olegych.scastie.client/components/editor/InteractiveProvider.scala
+++ b/client/src/main/scala/com.olegych.scastie.client/components/editor/InteractiveProvider.scala
@@ -1,18 +1,17 @@
package com.olegych.scastie.client.components.editor
+import scala.util.Try
+
import com.olegych.scastie.api
import com.olegych.scastie.client._
+import hooks.Hooks.UseStateF
import japgolly.scalajs.react._
+import scalajs.js
import typings.codemirrorState.mod._
import typings.codemirrorView.mod._
import typings.highlightJs.mod.{HighlightOptions => HLJSOptions}
-import typings.markedHighlight.mod._
import typings.marked.mod.marked.MarkedExtension
-
-import scala.util.Try
-
-import scalajs.js
-import hooks.Hooks.UseStateF
+import typings.markedHighlight.mod._
case class InteractiveProvider(
dependencies: Set[api.ScalaDependency],
@@ -20,13 +19,16 @@ case class InteractiveProvider(
metalsStatus: MetalsStatus,
updateStatus: MetalsStatus ~=> Callback,
isWorksheetMode: Boolean,
- isEmbedded: Boolean,
-) extends MetalsClient with MetalsAutocompletion with MetalsHover {
+ isEmbedded: Boolean
+) extends MetalsClient
+ with MetalsAutocompletion
+ with MetalsHover {
def extension: js.Array[Any] = js.Array[Any](
metalsHover,
metalsAutocomplete
)
+
}
object InteractiveProvider {
@@ -45,9 +47,10 @@ object InteractiveProvider {
val interactive = new Compartment()
val highlightJS = typings.highlightJs.mod.default
+
val highlightF: (String, String, String) => String = (str, lang, _) => {
if (lang != null && highlightJS.getLanguage(lang) != null && lang != "") {
- Try { highlightJS.highlight(str, HLJSOptions(lang)).value}.getOrElse(str)
+ Try { highlightJS.highlight(str, HLJSOptions(lang)).value }.getOrElse(str)
} else {
str
}
@@ -55,19 +58,20 @@ object InteractiveProvider {
val marked = typings.marked.mod.marked.`package`
marked.use(markedHighlight(SynchronousOptions.apply(highlightF)).asInstanceOf[MarkedExtension])
- marked.setOptions(typings.marked.mod.marked.MarkedOptions()
- .setHeaderIds(false)
- .setMangle(false)
+ marked.setOptions(
+ typings.marked.mod.marked
+ .MarkedOptions()
+ .setHeaderIds(false)
+ .setMangle(false)
)
private def wasMetalsToggled(prevProps: CodeEditor, props: CodeEditor): Boolean =
(prevProps.metalsStatus == MetalsDisabled && props.metalsStatus == MetalsLoading) ||
- (prevProps.metalsStatus != MetalsDisabled && props.metalsStatus == MetalsDisabled)
+ (prevProps.metalsStatus != MetalsDisabled && props.metalsStatus == MetalsDisabled)
- private def didConfigChange(prevProps: CodeEditor, props: CodeEditor): Boolean =
- props.target != prevProps.target ||
- props.dependencies != prevProps.dependencies ||
- props.isWorksheetMode != prevProps.isWorksheetMode
+ private def didConfigChange(prevProps: CodeEditor, props: CodeEditor): Boolean = props.target != prevProps.target ||
+ props.dependencies != prevProps.dependencies ||
+ props.isWorksheetMode != prevProps.isWorksheetMode
def reloadMetalsConfiguration(
editorView: UseStateF[CallbackTo, EditorView],
@@ -92,4 +96,3 @@ object InteractiveProvider {
}
}
-
diff --git a/client/src/main/scala/com.olegych.scastie.client/components/editor/MetalsAutocompletion.scala b/client/src/main/scala/com.olegych.scastie.client/components/editor/MetalsAutocompletion.scala
index 935225722..5e19e7130 100644
--- a/client/src/main/scala/com.olegych.scastie.client/components/editor/MetalsAutocompletion.scala
+++ b/client/src/main/scala/com.olegych.scastie.client/components/editor/MetalsAutocompletion.scala
@@ -1,89 +1,107 @@
package com.olegych.scastie.client.components.editor
+import scala.collection.mutable.HashMap
+import scala.concurrent.Future
+
import com.olegych.scastie.api
import japgolly.scalajs.react._
+import js.JSConverters._
import org.scalajs.dom
+import scalajs.concurrent.JSExecutionContext.Implicits.queue
+import scalajs.js
+import scalajs.js.Thenable.Implicits._
import typings.codemirrorAutocomplete.anon
import typings.codemirrorAutocomplete.mod._
import typings.codemirrorState.mod._
import typings.codemirrorView.mod._
-
-import scala.collection.mutable.HashMap
-import scala.concurrent.Future
-
-import scalajs.js
-import scalajs.concurrent.JSExecutionContext.Implicits.queue
-import scalajs.js.Thenable.Implicits._
-import js.JSConverters._
import EditorTextOps._
trait MetalsAutocompletion extends MetalsClient with DebouncingCapabilities {
- val jsRegex = js.RegExp("\\.?\\w*")
+ val jsRegex = js.RegExp("\\.?\\w*")
val selectionPattern = "\\$\\{\\d+:(.*?)\\}".r
var wasPreviousIncomplete = true
- var previousWord = ""
+ var previousWord = ""
val completionInfoCache = HashMap.empty[String, dom.Node]
/*
* Creates additionalInsertInstructions e.g autoimport for completions
*/
- private def createAdditionalTextEdits(insertInstructions: List[api.AdditionalInsertInstructions], view: EditorView): Seq[ChangeSpec] = {
+ private def createAdditionalTextEdits(
+ insertInstructions: List[api.AdditionalInsertInstructions],
+ view: EditorView
+ ): Seq[ChangeSpec] = {
insertInstructions.map(textEdit => {
val editRange = textEdit.editRange
- val startPos = view.state.doc.line(editRange.startLine).from.toInt + editRange.startChar
- val endPos = view.state.doc.line(editRange.endLine).from.toInt + editRange.endChar
- js.Dynamic.literal(
- from = startPos,
- to = endPos,
- insert = textEdit.text
- ).asInstanceOf[ChangeSpec]
+ val startPos = view.state.doc.line(editRange.startLine).from.toInt + editRange.startChar
+ val endPos = view.state.doc.line(editRange.endLine).from.toInt + editRange.endChar
+ js.Dynamic
+ .literal(
+ from = startPos,
+ to = endPos,
+ insert = textEdit.text
+ )
+ .asInstanceOf[ChangeSpec]
})
}
private def prepareInsertionText(completion: api.CompletionItemDTO, lineStart: Int): (EditorSelection, String) = {
- val patternIndex = completion.instructions.text.indexOf("$0")
+ val patternIndex = completion.instructions.text.indexOf("$0")
val partiallyCleanedPattern = completion.instructions.text.replace("$0", "")
- val offset = if (patternIndex == -1) {
- partiallyCleanedPattern.length
- } else {
- patternIndex
- }
+ val offset =
+ if (patternIndex == -1) {
+ partiallyCleanedPattern.length
+ } else {
+ patternIndex
+ }
val simpleSelection = EditorSelection.single(lineStart + offset)
- selectionPattern.findFirstMatchIn(partiallyCleanedPattern).map { regexMatch => {
- val offset = regexMatch.group(0).length - regexMatch.group(1).length
- val selection = EditorSelection.single(lineStart + regexMatch.start, lineStart + regexMatch.end - offset)
- val adjustedInsertString = partiallyCleanedPattern.substring(0, regexMatch.start) +
- regexMatch.group(1) +
- partiallyCleanedPattern.substring(regexMatch.end, partiallyCleanedPattern.length)
-
- (selection, adjustedInsertString)
- }}.getOrElse(simpleSelection, partiallyCleanedPattern)
+ selectionPattern
+ .findFirstMatchIn(partiallyCleanedPattern)
+ .map { regexMatch =>
+ {
+ val offset = regexMatch.group(0).length - regexMatch.group(1).length
+ val selection = EditorSelection.single(lineStart + regexMatch.start, lineStart + regexMatch.end - offset)
+ val adjustedInsertString = partiallyCleanedPattern.substring(0, regexMatch.start) +
+ regexMatch.group(1) +
+ partiallyCleanedPattern.substring(regexMatch.end, partiallyCleanedPattern.length)
+
+ (selection, adjustedInsertString)
+ }
+ }
+ .getOrElse(simpleSelection, partiallyCleanedPattern)
}
/*
* Creates edit transaction for completion. This enables cursor to be in proper possition after completion is accpeted
*/
- private def createEditTransaction(view: EditorView, completion: api.CompletionItemDTO, currentCursorPosition: Int): TransactionSpec = {
+ private def createEditTransaction(
+ view: EditorView,
+ completion: api.CompletionItemDTO,
+ currentCursorPosition: Int
+ ): TransactionSpec = {
val startLinePos = view.state.doc.line(completion.instructions.editRange.startLine).from
- val endLinePos = view.state.doc.line(completion.instructions.editRange.endLine).from
- val fromPos = startLinePos + completion.instructions.editRange.startChar
- val toPos = endLinePos + completion.instructions.editRange.endChar
+ val endLinePos = view.state.doc.line(completion.instructions.editRange.endLine).from
+ val fromPos = startLinePos + completion.instructions.editRange.startChar
+ val toPos = endLinePos + completion.instructions.editRange.endChar
val newCursorStartLine = fromPos +
completion.additionalInsertInstructions.foldLeft(0)(_ + _.text.length)
val (selection, insertText) = prepareInsertionText(completion, newCursorStartLine.toInt)
- TransactionSpec().setChangesVarargs(
- (js.Dynamic.literal(
- from = fromPos.toDouble,
- to = toPos.toDouble max currentCursorPosition,
- insert = insertText
- ).asInstanceOf[ChangeSpec] +: createAdditionalTextEdits(completion.additionalInsertInstructions, view)):_*
- ).setSelection(selection)
+ TransactionSpec()
+ .setChangesVarargs(
+ (js.Dynamic
+ .literal(
+ from = fromPos.toDouble,
+ to = toPos.toDouble max currentCursorPosition,
+ insert = insertText
+ )
+ .asInstanceOf[ChangeSpec] +: createAdditionalTextEdits(completion.additionalInsertInstructions, view)): _*
+ )
+ .setSelection(selection)
}
type CompletionInfoF = js.Function1[Completion, js.Promise[dom.Node]]
@@ -93,18 +111,22 @@ trait MetalsAutocompletion extends MetalsClient with DebouncingCapabilities {
*/
private def getCompletionInfo(completionItemDTO: api.CompletionItemDTO): CompletionInfoF = {
val key = completionItemDTO.symbol.getOrElse(completionItemDTO.label)
- lazy val maybeCachedResult = completionInfoCache.get(key)
+ lazy val maybeCachedResult = completionInfoCache
+ .get(key)
.map(node => js.Promise.resolve[dom.Node](node))
.getOrElse {
- makeRequest(api.CompletionInfoRequest(scastieMetalsOptions, completionItemDTO), "completionItemResolve")
- .map { maybeText =>
- parseMetalsResponse[String](maybeText).filter(_.nonEmpty).map { completionInfo =>
- val node = dom.document.createElement("div")
- node.innerHTML = InteractiveProvider.marked(completionInfo)
- completionInfoCache.put(key, node)
- node
- }.getOrElse(null)
- }.toJSPromise
+ makeRequest(api.CompletionInfoRequest(scastieMetalsOptions, completionItemDTO), "completionItemResolve").map {
+ maybeText =>
+ parseMetalsResponse[String](maybeText)
+ .filter(_.nonEmpty)
+ .map { completionInfo =>
+ val node = dom.document.createElement("div")
+ node.innerHTML = InteractiveProvider.marked(completionInfo)
+ completionInfoCache.put(key, node)
+ node
+ }
+ .getOrElse(null)
+ }.toJSPromise
}
val result: CompletionInfoF = (completion: Completion) => maybeCachedResult
@@ -117,8 +139,8 @@ trait MetalsAutocompletion extends MetalsClient with DebouncingCapabilities {
if (!matchesPreviousToken) wasPreviousIncomplete = true
})
- private val completionsF: js.Function1[CompletionContext, js.Promise[CompletionResult]] = {
- ctx => ifSupported {
+ private val completionsF: js.Function1[CompletionContext, js.Promise[CompletionResult]] = { ctx =>
+ ifSupported {
val word = ctx.matchBefore(jsRegex).asInstanceOf[anon.Text]
if (!ctx.explicit || (word == null || word.text.isEmpty || (word.from == word.to))) {
@@ -128,13 +150,20 @@ trait MetalsAutocompletion extends MetalsClient with DebouncingCapabilities {
previousWord = word.text
val request = toLSPRequest(ctx.state.doc.toString(), ctx.pos.toInt)
- val from = if (word.text.headOption == Some('.')) word.from + 1 else word.from
+ val from = if (word.text.headOption == Some('.')) word.from + 1 else word.from
makeRequest(request, "complete").map(maybeText =>
parseMetalsResponse[api.ScalaCompletionList](maybeText).map { completionList =>
val completions = completionList.items.map {
- case cmp @ api.CompletionItemDTO(name, detail, tpe, boost, insertInstructions, additionalInsertInstructions, symbol) =>
- Completion(name.stripSuffix(detail))
+ case cmp @ api.CompletionItemDTO(
+ name,
+ detail,
+ tpe,
+ boost,
+ insertInstructions,
+ additionalInsertInstructions,
+ symbol
+ ) => Completion(name.stripSuffix(detail))
.setDetail(detail)
.setInfo(getCompletionInfo(cmp))
.setType(tpe)
@@ -142,8 +171,7 @@ trait MetalsAutocompletion extends MetalsClient with DebouncingCapabilities {
.setApplyFunction4((view, _, from, to) => {
wasPreviousIncomplete = false
Callback(view.dispatch(createEditTransaction(view, cmp, to.toInt)))
- }
- )
+ })
}
wasPreviousIncomplete = completionList.isIncomplete
val result = CompletionResult(from, completions.toJSArray)
@@ -158,12 +186,15 @@ trait MetalsAutocompletion extends MetalsClient with DebouncingCapabilities {
private val autocompletionConfig = CompletionConfig()
.setInteractionDelay(0) // we want completions to work instantly
.setOverrideVarargs(completionsF)
- .setActivateOnTyping(false) // we use our own autocompletion trigger with working debounce MetalsAutocompletion.autocompletionTrigger
+ .setActivateOnTyping(
+ false
+ ) // we use our own autocompletion trigger with working debounce MetalsAutocompletion.autocompletionTrigger
.setIcons(true)
.setDefaultKeymap(true)
def metalsAutocomplete: js.Array[Any] = js.Array[Any](
autocompletion(autocompletionConfig),
- autocompletionTrigger,
+ autocompletionTrigger
)
+
}
diff --git a/client/src/main/scala/com.olegych.scastie.client/components/editor/MetalsClient.scala b/client/src/main/scala/com.olegych.scastie.client/components/editor/MetalsClient.scala
index 59f7e2acf..221016865 100644
--- a/client/src/main/scala/com.olegych.scastie.client/components/editor/MetalsClient.scala
+++ b/client/src/main/scala/com.olegych.scastie.client/components/editor/MetalsClient.scala
@@ -1,22 +1,21 @@
package com.olegych.scastie.client.components.editor
+import scala.concurrent.Future
+import scala.util.Failure
+import scala.util.Success
+
import com.olegych.scastie.api
import com.olegych.scastie.api.EitherFormat.JsEither._
import com.olegych.scastie.client._
import japgolly.scalajs.react._
+import js.JSConverters._
import org.scalajs.dom
import play.api.libs.json.Json
import play.api.libs.json.Reads
import play.api.libs.json.Writes
-
-import scala.concurrent.Future
-import scala.util.Failure
-import scala.util.Success
-
-import scalajs.js
import scalajs.concurrent.JSExecutionContext.Implicits.queue
+import scalajs.js
import scalajs.js.Thenable.Implicits._
-import js.JSConverters._
trait MetalsClient {
val updateStatus: MetalsStatus ~=> Callback
@@ -35,9 +34,9 @@ trait MetalsClient {
parseMetalsResponse[Boolean](maybeText).getOrElse(false)
)
res.onComplete {
- case Success(true) => updateStatus(MetalsReady).runNow()
+ case Success(true) => updateStatus(MetalsReady).runNow()
case Failure(exception) => updateStatus(NetworkError(exception.getMessage)).runNow()
- case _ =>
+ case _ =>
}
res
}
@@ -47,15 +46,17 @@ trait MetalsClient {
* Runs function `f` only when current scastie configuration is supported.
*/
protected def ifSupported[A](f: => Future[Option[A]]): js.Promise[Option[A]] = {
- isConfigurationSupported.flatMap(isSupported => {
- if (isSupported) {
- updateStatus(MetalsLoading).runNow()
- val res = f.map(Option(_))
- res.onComplete(_ => updateStatus(MetalsReady).runNow())
- res
- } else
- Future.successful(None)
- }).map(_.flatten).toJSPromise
+ isConfigurationSupported
+ .flatMap(isSupported => {
+ if (isSupported) {
+ updateStatus(MetalsLoading).runNow()
+ val res = f.map(Option(_))
+ res.onComplete(_ => updateStatus(MetalsReady).runNow())
+ res
+ } else Future.successful(None)
+ })
+ .map(_.flatten)
+ .toJSPromise
}
protected def toLSPRequest(code: String, offset: Int): api.LSPRequestDTO = {
@@ -63,21 +64,29 @@ trait MetalsClient {
api.LSPRequestDTO(scastieMetalsOptions, offsetParams)
}
- protected def makeRequest[A](req: A, endpoint: String)(implicit writes: Writes[A]): Future[Option[String]] = {
+ protected def makeRequest[A](req: A, endpoint: String)(
+ implicit writes: Writes[A]
+ ): Future[Option[String]] = {
val location = dom.window.location
// this is workaround until we migrate all services to proper docker setup or unify the servers
- val apiBase = if (location.hostname == "localhost") {
- location.protocol ++ "//" ++ location.hostname + ":" ++ "8000"
- } else ""
+ val apiBase =
+ if (location.hostname == "localhost") {
+ location.protocol ++ "//" ++ location.hostname + ":" ++ "8000"
+ } else ""
// We don't support metals in embedded so we don't need to map server url
- val request = dom.fetch(s"$apiBase/metals/$endpoint", js.Dynamic.literal(
- body = Json.toJson(req).toString,
- method = dom.HttpMethod.POST
- ).asInstanceOf[dom.RequestInit])
+ val request = dom.fetch(
+ s"$apiBase/metals/$endpoint",
+ js.Dynamic
+ .literal(
+ body = Json.toJson(req).toString,
+ method = dom.HttpMethod.POST
+ )
+ .asInstanceOf[dom.RequestInit]
+ )
for {
- res <- request
+ res <- request
text <- res.text()
} yield {
if (res.ok) Some(text)
@@ -88,20 +97,21 @@ trait MetalsClient {
}
}
- protected def parseMetalsResponse[A](maybeJsonText: Option[String])(implicit readsB: Reads[A]): Option[A] = {
+ protected def parseMetalsResponse[A](maybeJsonText: Option[String])(
+ implicit readsB: Reads[A]
+ ): Option[A] = {
maybeJsonText.flatMap(jsonText => {
Json.parse(jsonText).asOpt[Either[api.FailureType, A]] match {
- case None =>
- None
+ case None => None
case Some(Left(api.PresentationCompilerFailure(msg))) =>
updateStatus(MetalsConfigurationError(msg)).runNow()
None
- case Some(Left(api.NoResult(msg))) =>
- None
+ case Some(Left(api.NoResult(msg))) => None
case Some(Right(value)) =>
updateStatus(MetalsReady)
Some(value)
}
})
}
+
}
diff --git a/client/src/main/scala/com.olegych.scastie.client/components/editor/MetalsHover.scala b/client/src/main/scala/com.olegych.scastie.client/components/editor/MetalsHover.scala
index 3484551ce..e2042ce02 100644
--- a/client/src/main/scala/com.olegych.scastie.client/components/editor/MetalsHover.scala
+++ b/client/src/main/scala/com.olegych.scastie.client/components/editor/MetalsHover.scala
@@ -2,35 +2,37 @@ package com.olegych.scastie.client.components.editor
import com.olegych.scastie.api
import japgolly.scalajs.react._
+import js.JSConverters._
import org.scalajs.dom
-import typings.codemirrorState.mod._
-import typings.codemirrorView.mod._
-
-import scalajs.js
import scalajs.concurrent.JSExecutionContext.Implicits.queue
+import scalajs.js
import scalajs.js.Thenable.Implicits._
-import js.JSConverters._
+import typings.codemirrorState.mod._
+import typings.codemirrorView.mod._
trait MetalsHover extends MetalsClient {
- private val hovers = hoverTooltip((view, pos, _) => ifSupported {
- val request = toLSPRequest(view.state.doc.toString(), pos.toInt)
- makeRequest(request, "hover").map(maybeText =>
- parseMetalsResponse[api.HoverDTO](maybeText).map { hover =>
- val hoverF: js.Function1[EditorView, TooltipView] = _ => {
- val node = dom.document.createElement("div")
- node.innerHTML = InteractiveProvider.marked(hover.content)
- TooltipView(node.domToHtml.get)
- }
+ private val hovers = hoverTooltip((view, pos, _) =>
+ ifSupported {
+ val request = toLSPRequest(view.state.doc.toString(), pos.toInt)
+
+ makeRequest(request, "hover").map(maybeText =>
+ parseMetalsResponse[api.HoverDTO](maybeText).map { hover =>
+ val hoverF: js.Function1[EditorView, TooltipView] = _ => {
+ val node = dom.document.createElement("div")
+ node.innerHTML = InteractiveProvider.marked(hover.content)
+ TooltipView(node.domToHtml.get)
+ }
- view.state.wordAt(pos) match {
- case range: SelectionRange => Tooltip(hoverF, range.from)
- .setEnd(range.to)
- case _ => Tooltip(hoverF, pos)
+ view.state.wordAt(pos) match {
+ case range: SelectionRange => Tooltip(hoverF, range.from)
+ .setEnd(range.to)
+ case _ => Tooltip(hoverF, pos)
+ }
}
- }
- )
- }.map(_.getOrElse(null)).toJSPromise)
+ )
+ }.map(_.getOrElse(null)).toJSPromise
+ )
def metalsHover = hovers
}
diff --git a/client/src/main/scala/com.olegych.scastie.client/components/editor/OnChangeHandler.scala b/client/src/main/scala/com.olegych.scastie.client/components/editor/OnChangeHandler.scala
index e8a0e208a..c46e00f37 100644
--- a/client/src/main/scala/com.olegych.scastie.client/components/editor/OnChangeHandler.scala
+++ b/client/src/main/scala/com.olegych.scastie.client/components/editor/OnChangeHandler.scala
@@ -1,11 +1,10 @@
package com.olegych.scastie.client.components.editor
import japgolly.scalajs.react._
+import scalajs.js
import typings.codemirrorState.mod._
import typings.codemirrorView.mod._
-import scalajs.js
-
class OnChangeHandler(onChange: String ~=> Callback) extends js.Object {
private def scalaUpdate: js.Function1[ViewUpdate, Unit] = viewUpdate => {
@@ -19,6 +18,5 @@ class OnChangeHandler(onChange: String ~=> Callback) extends js.Object {
}
object OnChangeHandler {
- def apply(onChange: String ~=> Callback): Extension =
- ViewPlugin.define(_ => new OnChangeHandler(onChange)).extension
+ def apply(onChange: String ~=> Callback): Extension = ViewPlugin.define(_ => new OnChangeHandler(onChange)).extension
}
diff --git a/client/src/main/scala/com.olegych.scastie.client/components/editor/SimpleEditor.scala b/client/src/main/scala/com.olegych.scastie.client/components/editor/SimpleEditor.scala
index 3bb6dad4a..67e0c814a 100644
--- a/client/src/main/scala/com.olegych.scastie.client/components/editor/SimpleEditor.scala
+++ b/client/src/main/scala/com.olegych.scastie.client/components/editor/SimpleEditor.scala
@@ -1,75 +1,78 @@
package com.olegych.scastie.client.components.editor
import com.olegych.scastie.client.components.editor.OnChangeHandler
+import hooks.Hooks.UseStateF
import japgolly.scalajs.react._
import org.scalajs.dom.Element
+import scalajs.js
import typings.codemirrorLanguage.mod
import typings.codemirrorState.mod._
import typings.codemirrorView.mod._
-
-import scalajs.js
import vdom.all._
-import hooks.Hooks.UseStateF
final case class SimpleEditor(
- readOnly: Boolean,
- value: String,
- isDarkTheme: Boolean,
- onChange: String ~=> Callback,
+ readOnly: Boolean,
+ value: String,
+ isDarkTheme: Boolean,
+ onChange: String ~=> Callback
) extends Editor {
@inline def render: VdomElement = SimpleEditor.hooksComponent(this)
}
object SimpleEditor {
- private def init(props: SimpleEditor, ref: Ref.Simple[Element], editorView: UseStateF[CallbackTo, EditorView]): Callback =
- ref.foreachCB(divRef => {
- val basicExtensions = js.Array[Any](
- Editor.editorTheme.of(props.codemirrorTheme),
- Editor.indentationMarkersExtension,
- typings.codemirror.mod.minimalSetup,
- mod.StreamLanguage.define(typings.codemirrorLegacyModes.modeClikeMod.scala_),
- SyntaxHighlightingTheme.highlightingTheme,
- )
- lazy val readOnlyExtensions = js.Array[Any](
- EditorState.readOnly.of(true),
- )
- lazy val editableExtensions = js.Array[Any](
- lineNumbers(),
- OnChangeHandler(props.onChange),
- )
- val editorStateConfig = EditorStateConfig()
- .setDoc(props.value)
- .setExtensions {
- (if (props.readOnly) readOnlyExtensions else editableExtensions) ++ basicExtensions
- }
+ private def init(
+ props: SimpleEditor,
+ ref: Ref.Simple[Element],
+ editorView: UseStateF[CallbackTo, EditorView]
+ ): Callback = ref.foreachCB(divRef => {
+ val basicExtensions = js.Array[Any](
+ Editor.editorTheme.of(props.codemirrorTheme),
+ Editor.indentationMarkersExtension,
+ typings.codemirror.mod.minimalSetup,
+ mod.StreamLanguage.define(typings.codemirrorLegacyModes.modeClikeMod.scala_),
+ SyntaxHighlightingTheme.highlightingTheme
+ )
+ lazy val readOnlyExtensions = js.Array[Any](
+ EditorState.readOnly.of(true)
+ )
+ lazy val editableExtensions = js.Array[Any](
+ lineNumbers(),
+ OnChangeHandler(props.onChange)
+ )
+ val editorStateConfig = EditorStateConfig()
+ .setDoc(props.value)
+ .setExtensions {
+ (if (props.readOnly) readOnlyExtensions else editableExtensions) ++ basicExtensions
+ }
- val editor = new EditorView(EditorViewConfig()
+ val editor = new EditorView(
+ EditorViewConfig()
.setState(EditorState.create(editorStateConfig))
.setParent(divRef)
- )
+ )
- editorView.setState(editor)
- })
+ editorView.setState(editor)
+ })
private def updateComponent(
- props: SimpleEditor,
- ref: Ref.Simple[Element],
- prevProps: Option[SimpleEditor],
- editorView: UseStateF[CallbackTo, EditorView]
+ props: SimpleEditor,
+ ref: Ref.Simple[Element],
+ prevProps: Option[SimpleEditor],
+ editorView: UseStateF[CallbackTo, EditorView]
): Callback = {
Editor.updateCode(editorView, props) >>
Editor.updateTheme(ref, prevProps, props, editorView)
}
- val hooksComponent =
- ScalaFnComponent
- .withHooks[SimpleEditor]
- .useRef(Ref[Element])
- .useState(new EditorView())
- .useRef[Option[SimpleEditor]](None)
- .useLayoutEffectOnMountBy((props, ref, editorView, prevProps) => init(props, ref.value, editorView))
- .useEffectBy((props, ref, editorRef, prevProps) => updateComponent(props, ref.value, prevProps.value, editorRef))
- .useEffectBy((props, _, editorRef, prevProps) => prevProps.set(Some(props)))
- .render((props, ref, _, prevProps) => Editor.render(ref.value))
+ val hooksComponent = ScalaFnComponent
+ .withHooks[SimpleEditor]
+ .useRef(Ref[Element])
+ .useState(new EditorView())
+ .useRef[Option[SimpleEditor]](None)
+ .useLayoutEffectOnMountBy((props, ref, editorView, prevProps) => init(props, ref.value, editorView))
+ .useEffectBy((props, ref, editorRef, prevProps) => updateComponent(props, ref.value, prevProps.value, editorRef))
+ .useEffectBy((props, _, editorRef, prevProps) => prevProps.set(Some(props)))
+ .render((props, ref, _, prevProps) => Editor.render(ref.value))
+
}
diff --git a/client/src/main/scala/com.olegych.scastie.client/components/editor/SyntaxHighlightingHandler.scala b/client/src/main/scala/com.olegych.scastie.client/components/editor/SyntaxHighlightingHandler.scala
index b1868ae7d..e8faee49d 100644
--- a/client/src/main/scala/com.olegych.scastie.client/components/editor/SyntaxHighlightingHandler.scala
+++ b/client/src/main/scala/com.olegych.scastie.client/components/editor/SyntaxHighlightingHandler.scala
@@ -1,33 +1,34 @@
package com.olegych.scastie.client.components.editor
-import typings.codemirrorState.mod.ChangeSet
-import typings.codemirrorState.mod._
-import typings.codemirrorView.mod._
-import typings.webTreeSitter.mod._
-
import scala.collection.mutable.ListBuffer
import scalajs.js
+import typings.codemirrorState.mod._
+import typings.codemirrorState.mod.ChangeSet
+import typings.codemirrorView.mod._
+import typings.webTreeSitter.mod._
-class SyntaxHighlightingHandler(parser: Parser, language: Language, query: Query, initialState: String) extends js.Object {
+class SyntaxHighlightingHandler(parser: Parser, language: Language, query: Query, initialState: String)
+ extends js.Object {
val queryCaptureNames = query.captureNames
- var tree = parser.parse(initialState)
+ var tree = parser.parse(initialState)
var decorations: DecorationSet = computeDecorations()
private def computeDecorations(): DecorationSet = {
val rangeSetBuilder = new RangeSetBuilder[Decoration]()
- val captures = query.captures(tree.rootNode)
+ val captures = query.captures(tree.rootNode)
- captures.foldLeft(Option.empty[QueryCapture]){ (previousCapture, currentCapture) =>
+ captures.foldLeft(Option.empty[QueryCapture]) { (previousCapture, currentCapture) =>
if (!previousCapture.exists(_ == currentCapture)) {
val startPosition = currentCapture.node.startIndex
- val endPosition = currentCapture.node.endIndex
+ val endPosition = currentCapture.node.endIndex
val mark = Decoration.mark(
MarkDecorationSpec()
.setInclusive(true)
- .setClass(currentCapture.name.replace(".", "-")))
+ .setClass(currentCapture.name.replace(".", "-"))
+ )
rangeSetBuilder.add(startPosition, endPosition, mark)
}
@@ -45,12 +46,14 @@ class SyntaxHighlightingHandler(parser: Parser, language: Language, query: Query
private def mapChangesToTSEdits(changes: ChangeSet, originalText: Text, newText: Text): List[Edit] = {
val editBuffer = new ListBuffer[Edit]()
- changes.iterChanges { (fromA: Double, toA: Double, _, toB: Double, _) => {
- val oldEndPosition = indexToTSPoint(originalText, toA)
- val newEndPosition = indexToTSPoint(newText, toB)
- val startPosition = indexToTSPoint(originalText, fromA)
- editBuffer.addOne(Edit(toB, newEndPosition, toA, oldEndPosition, fromA, startPosition))
- }}
+ changes.iterChanges { (fromA: Double, toA: Double, _, toB: Double, _) =>
+ {
+ val oldEndPosition = indexToTSPoint(originalText, toA)
+ val newEndPosition = indexToTSPoint(newText, toB)
+ val startPosition = indexToTSPoint(originalText, fromA)
+ editBuffer.addOne(Edit(toB, newEndPosition, toA, oldEndPosition, fromA, startPosition))
+ }
+ }
editBuffer.toList
@@ -59,10 +62,11 @@ class SyntaxHighlightingHandler(parser: Parser, language: Language, query: Query
var update: js.Function1[ViewUpdate, Unit] = viewUpdate => {
if (viewUpdate.docChanged) {
val newText = viewUpdate.state.doc.toString
- val edits = mapChangesToTSEdits(viewUpdate.changes, viewUpdate.startState.doc, viewUpdate.state.doc)
+ val edits = mapChangesToTSEdits(viewUpdate.changes, viewUpdate.startState.doc, viewUpdate.state.doc)
edits.foreach(tree.edit)
tree = parser.parse(newText, tree)
decorations = computeDecorations()
}
}
+
}
diff --git a/client/src/main/scala/com.olegych.scastie.client/components/editor/SyntaxHighlightingPlugin.scala b/client/src/main/scala/com.olegych.scastie.client/components/editor/SyntaxHighlightingPlugin.scala
index 5b0ac9252..0e9110c9e 100644
--- a/client/src/main/scala/com.olegych.scastie.client/components/editor/SyntaxHighlightingPlugin.scala
+++ b/client/src/main/scala/com.olegych.scastie.client/components/editor/SyntaxHighlightingPlugin.scala
@@ -1,45 +1,45 @@
package com.olegych.scastie.client.components.editor
-import typings.webTreeSitter.mod._
+import japgolly.scalajs.react._
import org.scalablytyped.runtime.StObject
+import org.scalajs.dom
import org.scalajs.macrotaskexecutor.MacrotaskExecutor.Implicits._
-import japgolly.scalajs.react._
+import scalajs.js
import typings.codemirrorState.mod._
import typings.codemirrorView.mod._
-import scalajs.js
-import org.scalajs.dom
+import typings.webTreeSitter.mod._
class SyntaxHighlightingPlugin(editorView: hooks.Hooks.UseStateF[CallbackTo, EditorView]) {
val syntaxHighlightingExtension = new Compartment()
- val fallbackExtension = typings.codemirrorLanguage.mod.StreamLanguage.define(typings.codemirrorLegacyModes.modeClikeMod.scala_).extension
+ val fallbackExtension =
+ typings.codemirrorLanguage.mod.StreamLanguage.define(typings.codemirrorLegacyModes.modeClikeMod.scala_).extension
val location = dom.window.location
+
// this is workaround until we migrate all services to proper docker setup or unify the servers
- val apiBase = if (location.hostname == "localhost") {
- location.protocol ++ "//" ++ location.hostname + ":" ++ "9000"
- } else if (location.protocol == "file:") {
- "http://localhost:9000"
- } else {
- "https://scastie.scala-lang.org"
- }
+ val apiBase =
+ if (location.hostname == "localhost") {
+ location.protocol ++ "//" ++ location.hostname + ":" ++ "9000"
+ } else if (location.protocol == "file:") {
+ "http://localhost:9000"
+ } else {
+ "https://scastie.scala-lang.org"
+ }
val initOptions = new js.Object {
- val apiBaseField = apiBase
- def locateFile(scriptName: String, scriptDirectory: String): String =
- s"$apiBaseField/public/tree-sitter.wasm"
+ val apiBaseField = apiBase
+ def locateFile(scriptName: String, scriptDirectory: String): String = s"$apiBaseField/public/tree-sitter.wasm"
}
- private val fetchTSWasm = init(initOptions)
- .toFuture
+ private val fetchTSWasm = init(initOptions).toFuture
.flatMap(_ => Language.load(s"$apiBase/public/tree-sitter-scala.wasm").toFuture)
-
val highlightQuery = dom.fetch(s"$apiBase/public/highlights.scm")
for {
language <- fetchTSWasm
- query <- highlightQuery.toFuture
- text <- query.text().toFuture
+ query <- highlightQuery.toFuture
+ text <- query.text().toFuture
} yield {
val parser = new TreesitterParser()
parser.setLanguage(language)
@@ -48,16 +48,21 @@ class SyntaxHighlightingPlugin(editorView: hooks.Hooks.UseStateF[CallbackTo, Edi
}
def switchToTreesitterParser(scalaParser: Parser, language: Language, query: Query): Unit = {
- val extension = ViewPlugin.define(editorView =>
- new SyntaxHighlightingHandler(scalaParser, language, query, editorView.state.doc.toString),
- PluginSpec[SyntaxHighlightingHandler]().setDecorations(_.decorations)
- ).extension
+ val extension = ViewPlugin
+ .define(
+ editorView => new SyntaxHighlightingHandler(scalaParser, language, query, editorView.state.doc.toString),
+ PluginSpec[SyntaxHighlightingHandler]().setDecorations(_.decorations)
+ )
+ .extension
- val effects = syntaxHighlightingExtension.reconfigure(extension)
+ val effects = syntaxHighlightingExtension.reconfigure(extension)
val transactionSpec = TransactionSpec().setEffects(effects)
- editorView.modState(editorView => {
+ editorView
+ .modState(editorView => {
editorView.dispatch(transactionSpec)
editorView
- }).runNow()
+ })
+ .runNow()
}
+
}
diff --git a/client/src/main/scala/com.olegych.scastie.client/components/editor/SyntaxHighlightingTheme.scala b/client/src/main/scala/com.olegych.scastie.client/components/editor/SyntaxHighlightingTheme.scala
index 8c8fef442..d779fa5ef 100644
--- a/client/src/main/scala/com.olegych.scastie.client/components/editor/SyntaxHighlightingTheme.scala
+++ b/client/src/main/scala/com.olegych.scastie.client/components/editor/SyntaxHighlightingTheme.scala
@@ -1,10 +1,9 @@
package com.olegych.scastie.client.components.editor
+import scalajs.js
import typings.codemirrorLanguage.mod
import typings.lezerHighlight.mod.tags
-import scalajs.js
-
object SyntaxHighlightingTheme {
private val highlightStyle = mod.HighlightStyle.define(
@@ -63,7 +62,7 @@ object SyntaxHighlightingTheme {
mod.TagStyle(tags.typeName).setClass("type"),
mod.TagStyle(tags.typeOperator).setClass("type-qualifier"),
mod.TagStyle(tags.unit).setClass("none"),
- mod.TagStyle(tags.variableName).setClass("function"),
+ mod.TagStyle(tags.variableName).setClass("function")
)
)
diff --git a/client/src/main/scala/com.olegych.scastie.client/components/editor/TreesitterParser.scala b/client/src/main/scala/com.olegych.scastie.client/components/editor/TreesitterParser.scala
index b9b9d3400..9cfdb341e 100644
--- a/client/src/main/scala/com.olegych.scastie.client/components/editor/TreesitterParser.scala
+++ b/client/src/main/scala/com.olegych.scastie.client/components/editor/TreesitterParser.scala
@@ -1,13 +1,12 @@
package com.olegych.scastie.client.components.editor
-import typings.webTreeSitter.mod.Parser
+import scala.scalajs.js.annotation.JSGlobal
+
import org.scalablytyped.runtime.StObject
import scalajs.js
import scalajs.js.annotation.JSImport
-import scala.scalajs.js.annotation.JSGlobal
+import typings.webTreeSitter.mod.Parser
@JSGlobal("Treesitter")
@js.native
-class TreesitterParser() extends StObject with Parser {
-
-}
+class TreesitterParser() extends StObject with Parser {}
diff --git a/client/src/main/scala/com.olegych.scastie.client/components/package.scala b/client/src/main/scala/com.olegych.scastie.client/components/package.scala
index 48cfb7c85..825d1680b 100644
--- a/client/src/main/scala/com.olegych.scastie.client/components/package.scala
+++ b/client/src/main/scala/com.olegych.scastie.client/components/package.scala
@@ -1,101 +1,71 @@
package com.olegych.scastie.client
import com.olegych.scastie.api._
-
+import japgolly.scalajs.react.Callback
import japgolly.scalajs.react.Reusability
import japgolly.scalajs.react.Reusable
-import japgolly.scalajs.react.Callback
import org.scalajs.dom.HTMLElement
package object components {
val reusableEmpty: Reusable[Callback] = Reusable.always(Callback.empty)
- implicit val reusabilityInputs: Reusability[Inputs] =
- Reusability.byRefOr_==
+ implicit val reusabilityInputs: Reusability[Inputs] = Reusability.byRefOr_==
- implicit val reusabilityUser: Reusability[User] =
- Reusability.byRef || Reusability.derive[User]
+ implicit val reusabilityUser: Reusability[User] = Reusability.byRef || Reusability.derive[User]
- implicit val snippetIdReuse: Reusability[SnippetId] =
- Reusability.byRefOr_==
+ implicit val snippetIdReuse: Reusability[SnippetId] = Reusability.byRefOr_==
- implicit val viewReuse: Reusability[View] =
- Reusability.byRefOr_==
+ implicit val viewReuse: Reusability[View] = Reusability.byRefOr_==
- implicit val scalaTargetReuse: Reusability[ScalaTarget] =
- Reusability.byRefOr_==
+ implicit val scalaTargetReuse: Reusability[ScalaTarget] = Reusability.byRefOr_==
- implicit val pageReuse: Reusability[Page] =
- Reusability.byRefOr_==
+ implicit val pageReuse: Reusability[Page] = Reusability.byRefOr_==
- implicit val scalaTargetTypeReuse: Reusability[ScalaTargetType] =
- Reusability.byRefOr_==
+ implicit val scalaTargetTypeReuse: Reusability[ScalaTargetType] = Reusability.byRefOr_==
- implicit val scalaScalaDependency: Reusability[ScalaDependency] =
- Reusability.byRefOr_==
+ implicit val scalaScalaDependency: Reusability[ScalaDependency] = Reusability.byRefOr_==
- implicit val attachedDomsReuse: Reusability[Map[String, HTMLElement]] =
- Reusability.byRef ||
- Reusability.by(_.keys.toSet)
+ implicit val attachedDomsReuse: Reusability[Map[String, HTMLElement]] = Reusability.byRef ||
+ Reusability.by(_.keys.toSet)
- implicit val releaseOptionsReuse: Reusability[ReleaseOptions] =
- Reusability.byRefOr_==
+ implicit val releaseOptionsReuse: Reusability[ReleaseOptions] = Reusability.byRefOr_==
- implicit val projectReuse: Reusability[Project] =
- Reusability.byRefOr_==
+ implicit val projectReuse: Reusability[Project] = Reusability.byRefOr_==
- implicit val librariesFromReuse: Reusability[Map[ScalaDependency, Project]] =
- Reusability.byRefOr_==
+ implicit val librariesFromReuse: Reusability[Map[ScalaDependency, Project]] = Reusability.byRefOr_==
- implicit val instrumentationReuse: Reusability[Set[Instrumentation]] =
- Reusability.byRefOr_==
+ implicit val instrumentationReuse: Reusability[Set[Instrumentation]] = Reusability.byRefOr_==
- implicit val compilationInfosReuse: Reusability[Set[Problem]] =
- Reusability.byRefOr_==
+ implicit val compilationInfosReuse: Reusability[Set[Problem]] = Reusability.byRefOr_==
- implicit val runtimeErrorReuse: Reusability[Option[RuntimeError]] =
- Reusability.byRefOr_==
+ implicit val runtimeErrorReuse: Reusability[Option[RuntimeError]] = Reusability.byRefOr_==
- implicit val consoleOutputsReuse: Reusability[Vector[ConsoleOutput]] =
- Reusability.byRefOr_==
+ implicit val consoleOutputsReuse: Reusability[Vector[ConsoleOutput]] = Reusability.byRefOr_==
- implicit val snippetSummaryReuse: Reusability[List[SnippetSummary]] =
- Reusability.byRefOr_==
+ implicit val snippetSummaryReuse: Reusability[List[SnippetSummary]] = Reusability.byRefOr_==
- implicit val consoleStateReuse: Reusability[ConsoleState] =
- Reusability.byRefOr_==
+ implicit val consoleStateReuse: Reusability[ConsoleState] = Reusability.byRefOr_==
- implicit def reusabilityEventStream[T]: Reusability[EventStream[T]] =
- Reusability.always
+ implicit def reusabilityEventStream[T]: Reusability[EventStream[T]] = Reusability.always
- implicit val modalStateReuse: Reusability[ModalState] =
- Reusability.derive[ModalState]
+ implicit val modalStateReuse: Reusability[ModalState] = Reusability.derive[ModalState]
- implicit val snippetStateReuse: Reusability[SnippetState] =
- Reusability.derive[SnippetState]
+ implicit val snippetStateReuse: Reusability[SnippetState] = Reusability.derive[SnippetState]
- implicit val consoleOutputReuse: Reusability[ConsoleOutput] =
- Reusability.byRefOr_==
+ implicit val consoleOutputReuse: Reusability[ConsoleOutput] = Reusability.byRefOr_==
- implicit val outputsReuse: Reusability[Outputs] =
- Reusability.derive[Outputs]
+ implicit val outputsReuse: Reusability[Outputs] = Reusability.derive[Outputs]
- implicit val sbtRunnerStateReuse: Reusability[Option[Vector[SbtRunnerState]]] =
- Reusability.byRefOr_==
+ implicit val sbtRunnerStateReuse: Reusability[Option[Vector[SbtRunnerState]]] = Reusability.byRefOr_==
- implicit val statusStateReuse: Reusability[StatusState] =
- Reusability.derive[StatusState]
+ implicit val statusStateReuse: Reusability[StatusState] = Reusability.derive[StatusState]
- implicit val embeddedOptionsReuse: Reusability[EmbeddedOptions] =
- Reusability.derive[EmbeddedOptions]
+ implicit val embeddedOptionsReuse: Reusability[EmbeddedOptions] = Reusability.derive[EmbeddedOptions]
- implicit val metalsStatusReuse: Reusability[MetalsStatus] =
- Reusability.byRefOr_==
+ implicit val metalsStatusReuse: Reusability[MetalsStatus] = Reusability.byRefOr_==
- implicit val scastieStateReuse: Reusability[ScastieState] =
- Reusability.derive[ScastieState]
+ implicit val scastieStateReuse: Reusability[ScastieState] = Reusability.derive[ScastieState]
- implicit val scastieBackendReuse: Reusability[ScastieBackend] =
- Reusability.byRefOr_==
+ implicit val scastieBackendReuse: Reusability[ScastieBackend] = Reusability.byRefOr_==
}
diff --git a/client/src/main/scala/com.olegych.scastie.client/package.scala b/client/src/main/scala/com.olegych.scastie.client/package.scala
index ac7f8756b..c4304bed7 100644
--- a/client/src/main/scala/com.olegych.scastie.client/package.scala
+++ b/client/src/main/scala/com.olegych.scastie.client/package.scala
@@ -1,23 +1,21 @@
package com.olegych.scastie
-import play.api.libs.json._
-
import org.scalajs.dom.window
+import play.api.libs.json._
package object client {
def dontSerialize[T](fallback: T): Format[T] = new Format[T] {
- def writes(v: T): JsValue = JsNull
+ def writes(v: T): JsValue = JsNull
def reads(json: JsValue): JsResult[T] = JsSuccess(fallback)
}
def dontSerializeOption[T]: Format[T] = new Format[T] {
- def writes(v: T): JsValue = JsNull
+ def writes(v: T): JsValue = JsNull
def reads(json: JsValue): JsResult[T] = JsSuccess(null.asInstanceOf[T])
}
- def dontSerializeList[T]: Format[List[T]] =
- dontSerialize(List())
+ def dontSerializeList[T]: Format[List[T]] = dontSerialize(List())
val isMac: Boolean = window.navigator.userAgent.contains("Mac")
val isMobile: Boolean = "Android|webOS|Mobi|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini|Samsung".r.unanchored
diff --git a/deployment/announce.scala b/deployment/announce.scala
index 71688c536..592e8c1fe 100644
--- a/deployment/announce.scala
+++ b/deployment/announce.scala
@@ -1,34 +1,34 @@
// scp scastie@scastie.scala-lang.org:users.txt users.txt
+
object Announce {
import java.nio.file._
val users = Files.readAllLines(Paths.get("users.txt")).toArray
val slice = 50
- val from = 800
+ val from = 800
- users.drop(from).sliding(slice, slice).toList.zipWithIndex.foreach {
- case (group, i) =>
- val start = from + i * slice
- val end = start + group.size
+ users.drop(from).sliding(slice, slice).toList.zipWithIndex.foreach { case (group, i) =>
+ val start = from + i * slice
+ val end = start + group.size
- val message =
- s"""|Scastie Beta Opens ($start - $end)
- |
- |Hello,
- |
- |Thanks for signing up to the Beta. We are now opening spots $start to $end!
- |
- |This means you can go to https://scastie.scala-lang.org and you will now have access. Oh, and please, please, please give us feedback!
- |
- |${group.map(user => "@" + user).mkString(System.lineSeparator)}
- |
- |Thanks,
- |Guillaume""".stripMargin
+ val message =
+ s"""|Scastie Beta Opens ($start - $end)
+ |
+ |Hello,
+ |
+ |Thanks for signing up to the Beta. We are now opening spots $start to $end!
+ |
+ |This means you can go to https://scastie.scala-lang.org and you will now have access. Oh, and please, please, please give us feedback!
+ |
+ |${group.map(user => "@" + user).mkString(System.lineSeparator)}
+ |
+ |Thanks,
+ |Guillaume""".stripMargin
- val dest = Paths.get("beta", start.toString)
- if (Files.exists(dest)) {
- Files.delete(dest)
- }
- Files.write(dest, message.getBytes, StandardOpenOption.CREATE_NEW)
+ val dest = Paths.get("beta", start.toString)
+ if (Files.exists(dest)) {
+ Files.delete(dest)
+ }
+ Files.write(dest, message.getBytes, StandardOpenOption.CREATE_NEW)
}
}
diff --git a/instrumentation/src/main/scala/com.olegych.scastie.instrumentation/Instrument.scala b/instrumentation/src/main/scala/com.olegych.scastie.instrumentation/Instrument.scala
index 179510956..b1aa28780 100644
--- a/instrumentation/src/main/scala/com.olegych.scastie.instrumentation/Instrument.scala
+++ b/instrumentation/src/main/scala/com.olegych.scastie.instrumentation/Instrument.scala
@@ -1,112 +1,108 @@
package com.olegych.scastie.instrumentation
-import com.olegych.scastie.api.ScalaTarget._
-import com.olegych.scastie.api.{Inputs, Instrumentation, ScalaTarget, ScalaTargetType}
-
import scala.collection.immutable.Seq
import scala.meta._
import scala.meta.inputs.Position
import scala.meta.parsers.Parsed
import scala.util.control.NonFatal
+import com.olegych.scastie.api.{Inputs, Instrumentation, ScalaTarget, ScalaTargetType}
+import com.olegych.scastie.api.ScalaTarget._
+
sealed trait InstrumentationFailure
object InstrumentationFailure {
- case object HasMainMethod extends InstrumentationFailure
- case object UnsupportedDialect extends InstrumentationFailure
- case class ParsingError(error: Parsed.Error) extends InstrumentationFailure
+ case object HasMainMethod extends InstrumentationFailure
+ case object UnsupportedDialect extends InstrumentationFailure
+ case class ParsingError(error: Parsed.Error) extends InstrumentationFailure
case class InternalError(exception: Throwable) extends InstrumentationFailure
}
object Instrument {
+
def getParsingLineOffset(inputs: Inputs): Int = {
if (inputs.isWorksheetMode) -1 else 0
}
+
def getExceptionLineOffset(inputs: Inputs): Int = {
if (inputs.isWorksheetMode) -2 else 0
}
+
def getMessageLineOffset(inputs: Inputs): Int = {
if (inputs.isWorksheetMode) -2 else 0
}
import InstrumentationFailure._
- private val instrumentedObject = Instrumentation.instrumentedObject
+ private val instrumentedObject = Instrumentation.instrumentedObject
private val instrumentationMethod = "instrumentations$"
- private val instrumentationMap = "instrumentationMap$"
+ private val instrumentationMap = "instrumentationMap$"
- private val emptyMapT = "_root_.scala.collection.mutable.Map.empty"
- private val jsExportT = "_root_.scala.scalajs.js.annotation.JSExport"
- private val jsExportTopLevelT =
- "_root_.scala.scalajs.js.annotation.JSExportTopLevel"
+ private val emptyMapT = "_root_.scala.collection.mutable.Map.empty"
+ private val jsExportT = "_root_.scala.scalajs.js.annotation.JSExport"
+ private val jsExportTopLevelT = "_root_.scala.scalajs.js.annotation.JSExportTopLevel"
- private val apiPackage = "_root_.com.olegych.scastie.api"
- private val positionT = s"$apiPackage.Position"
- private val renderT = s"$apiPackage.Render"
- private val runtimeErrorT = s"$apiPackage.RuntimeError"
+ private val apiPackage = "_root_.com.olegych.scastie.api"
+ private val positionT = s"$apiPackage.Position"
+ private val renderT = s"$apiPackage.Render"
+ private val runtimeErrorT = s"$apiPackage.RuntimeError"
private val instrumentationT = s"$apiPackage.Instrumentation"
- private val runtimeT = s"$apiPackage.runtime.Runtime"
- private val domhookT = s"$apiPackage.runtime.DomHook"
+ private val runtimeT = s"$apiPackage.runtime.Runtime"
+ private val domhookT = s"$apiPackage.runtime.DomHook"
- private val elemArrayT =
- "_root_.scala.scalajs.js.Array[_root_.org.scalajs.dom.raw.HTMLElement]"
+ private val elemArrayT = "_root_.scala.scalajs.js.Array[_root_.org.scalajs.dom.raw.HTMLElement]"
private def posToApi(position: Position, offset: Int) = {
val (x, y) = position match {
- case Position.None => (0, 0)
- case Position.Range(_, start, end) =>
- (start - offset, end - offset)
+ case Position.None => (0, 0)
+ case Position.Range(_, start, end) => (start - offset, end - offset)
}
s"$positionT($x, $y)"
}
def instrumentOne(term: Term, tpeTree: Option[Type], offset: Int, isScalaJs: Boolean): Patch = {
- val treeQuote =
- tpeTree match {
- case None => s"val $$t = $term"
- case Some(tpe) => s"val $$t: $tpe = $term"
- }
+ val treeQuote = tpeTree match {
+ case None => s"val $$t = $term"
+ case Some(tpe) => s"val $$t: $tpe = $term"
+ }
val renderCall =
if (!isScalaJs) s"$runtimeT.render($$t);"
else s"$runtimeT.render($$t, attach _);"
- val replacement =
- Seq(
- "scala.Predef.locally {",
- treeQuote + "; ",
- s"$instrumentationMap(${posToApi(term.pos, offset)}) = $renderCall",
- "$t}"
- ).mkString("")
+ val replacement = Seq(
+ "scala.Predef.locally {",
+ treeQuote + "; ",
+ s"$instrumentationMap(${posToApi(term.pos, offset)}) = $renderCall",
+ "$t}"
+ ).mkString("")
Patch(term.tokens.head, term.tokens.last, replacement)
}
private def instrument(source: Source, offset: Int, isScalaJs: Boolean): String = {
- val instrumentedCodePatches =
- source.stats.collect {
- case c: Defn.Object if c.name.value == instrumentedObject =>
- val openCurlyBrace = c.templ.tokens.find(_.toString == "{").get
+ val instrumentedCodePatches = source.stats.collect {
+ case c: Defn.Object if c.name.value == instrumentedObject =>
+ val openCurlyBrace = c.templ.tokens.find(_.toString == "{").get
- val instrumentationMapCode = Seq(
- s"{ private val $instrumentationMap = $emptyMapT[$positionT, $renderT]",
- s"def $instrumentationMethod = $instrumentationMap.toList.map{ case (pos, r) => $instrumentationT(pos, r) }"
- ).mkString("", ";", ";")
+ val instrumentationMapCode = Seq(
+ s"{ private val $instrumentationMap = $emptyMapT[$positionT, $renderT]",
+ s"def $instrumentationMethod = $instrumentationMap.toList.map{ case (pos, r) => $instrumentationT(pos, r) }"
+ ).mkString("", ";", ";")
- val instrumentationMapPatch =
- Patch(openCurlyBrace, openCurlyBrace, instrumentationMapCode)
+ val instrumentationMapPatch = Patch(openCurlyBrace, openCurlyBrace, instrumentationMapCode)
- instrumentationMapPatch +:
- c.templ.stats
+ instrumentationMapPatch +:
+ c.templ.stats
.filter {
case _: Term.EndMarker => false
case _ => true
}
- .collect {
- case term: Term => instrumentOne(term, None, offset, isScalaJs)
+ .collect { case term: Term =>
+ instrumentOne(term, None, offset, isScalaJs)
}
- }.flatten
+ }.flatten
val instrumentedCode = Patch(source.tokens, instrumentedCodePatches)
@@ -144,9 +140,8 @@ object Instrument {
case _ => false
}
}
- val apps = Set("App", "IOApp")
- def hasApp(templ: Template): Boolean =
- templ.inits.exists(p => apps(p.syntax))
+ val apps = Set("App", "IOApp")
+ def hasApp(templ: Template): Boolean = templ.inits.exists(p => apps(p.syntax))
source.stats.exists {
case c: Defn.Object if c.name.value == instrumentedObject =>
@@ -163,7 +158,7 @@ object Instrument {
def apply(code: String, target: ScalaTarget): Either[InstrumentationFailure, String] = {
val runtimeImport = target match {
case Scala3(scalaVersion) => "import _root_.com.olegych.scastie.api.runtime.*"
- case _ => "import _root_.com.olegych.scastie.api.runtime._"
+ case _ => "import _root_.com.olegych.scastie.api.runtime._"
}
val isScalaJs = target.targetType == ScalaTargetType.JS
@@ -172,7 +167,7 @@ object Instrument {
if (!isScalaJs) s"object $instrumentedObject extends ScastieApp {"
else s"object $instrumentedObject extends ScastieApp with $domhookT {"
val prelude = s"""$runtimeImport\n$classBegin"""
- val code0 = s"""$prelude\n$code\n}"""
+ val code0 = s"""$prelude\n$code\n}"""
def typelevel(scalaVersion: String): Option[Dialect] = {
if (scalaVersion.startsWith("2.12")) Some(dialects.Typelevel212)
@@ -201,16 +196,15 @@ object Instrument {
try {
code0.parse[Source] match {
case parsed: Parsed.Success[_] =>
- if (!hasMainMethod(parsed.get))
- Right(instrument(parsed.get, prelude.length + 1, isScalaJs))
+ if (!hasMainMethod(parsed.get)) Right(instrument(parsed.get, prelude.length + 1, isScalaJs))
else Left(HasMainMethod)
case e: Parsed.Error => Left(ParsingError(e))
}
} catch {
- case NonFatal(e) =>
- Left(InternalError(e))
+ case NonFatal(e) => Left(InternalError(e))
}
case None => Left(UnsupportedDialect)
}
}
+
}
diff --git a/instrumentation/src/main/scala/com.olegych.scastie.instrumentation/InstrumentedInputs.scala b/instrumentation/src/main/scala/com.olegych.scastie.instrumentation/InstrumentedInputs.scala
index 51144df79..23c2f09c6 100644
--- a/instrumentation/src/main/scala/com.olegych.scastie.instrumentation/InstrumentedInputs.scala
+++ b/instrumentation/src/main/scala/com.olegych.scastie.instrumentation/InstrumentedInputs.scala
@@ -2,12 +2,12 @@ package com.olegych.scastie.instrumentation
import java.io.{PrintWriter, StringWriter}
import java.time.Instant
+import scala.meta.parsers.Parsed
import com.olegych.scastie.api._
-import scala.meta.parsers.Parsed
-
case class InstrumentationFailureReport(message: String, line: Option[Int]) {
+
def toProgress(snippetId: SnippetId): SnippetProgress = {
SnippetProgress.default.copy(
ts = Some(Instant.now.toEpochMilli),
@@ -15,9 +15,11 @@ case class InstrumentationFailureReport(message: String, line: Option[Int]) {
compilationInfos = List(Problem(Error, line, message))
)
}
+
}
object InstrumentedInputs {
+
def apply(inputs0: Inputs): Either[InstrumentationFailureReport, InstrumentedInputs] = {
if (inputs0.isWorksheetMode) {
val instrumented = Instrument(inputs0.code, inputs0.target).map { instrumentedCode =>
@@ -25,8 +27,7 @@ object InstrumentedInputs {
}
instrumented match {
- case Right(inputs) =>
- success(inputs)
+ case Right(inputs) => success(inputs)
case Left(error) =>
import InstrumentationFailure._
@@ -40,12 +41,14 @@ object InstrumentedInputs {
case ParsingError(error) =>
val lineOffset = Instrument.getParsingLineOffset(inputs0)
- val errorLine = (error.pos.startLine + lineOffset) max 1
- Right(InstrumentedInputs(
- inputs = inputs0.copy(code = error.pos.input.text),
- isForcedProgramMode = false,
- optionalParsingError = Some(InstrumentationFailureReport(error.message, Some(errorLine)))
- ))
+ val errorLine = (error.pos.startLine + lineOffset) max 1
+ Right(
+ InstrumentedInputs(
+ inputs = inputs0.copy(code = error.pos.input.text),
+ isForcedProgramMode = false,
+ optionalParsingError = Some(InstrumentationFailureReport(error.message, Some(errorLine)))
+ )
+ )
case InternalError(exception) =>
val errors = new StringWriter()
@@ -64,10 +67,11 @@ object InstrumentedInputs {
private def success(inputs: Inputs): Either[InstrumentationFailureReport, InstrumentedInputs] = {
Right(InstrumentedInputs(inputs, isForcedProgramMode = false))
}
+
}
case class InstrumentedInputs(
- inputs: Inputs,
- isForcedProgramMode: Boolean,
- optionalParsingError: Option[InstrumentationFailureReport] = None
+ inputs: Inputs,
+ isForcedProgramMode: Boolean,
+ optionalParsingError: Option[InstrumentationFailureReport] = None
)
diff --git a/instrumentation/src/main/scala/com.olegych.scastie.instrumentation/Patch.scala b/instrumentation/src/main/scala/com.olegych.scastie.instrumentation/Patch.scala
index bc61c344e..192ae1c8e 100644
--- a/instrumentation/src/main/scala/com.olegych.scastie.instrumentation/Patch.scala
+++ b/instrumentation/src/main/scala/com.olegych.scastie.instrumentation/Patch.scala
@@ -5,12 +5,13 @@ import scala.meta._
import scala.meta.tokens.Token
case class Patch(from: Token, to: Token, replace: String) {
- def insideRange(token: Token): Boolean =
- (token.input eq from.input) &&
- token.end <= to.end &&
- token.start >= from.start
+
+ def insideRange(token: Token): Boolean = (token.input eq from.input) &&
+ token.end <= to.end &&
+ token.start >= from.start
val tokens: scala.Seq[Token] = replace.tokenize.get.tokens.toSeq
+
def runOn(str: Seq[Token]): Seq[Token] = {
str.flatMap {
case `from` => tokens
@@ -18,20 +19,24 @@ case class Patch(from: Token, to: Token, replace: String) {
case x => Seq(x)
}
}
+
}
object Patch {
+
def verifyPatches(patches: Seq[Patch]): Unit = {
// TODO(olafur) assert there's no conflicts.
}
+
def apply(input: Seq[Token], patches: Seq[Patch]): String = {
verifyPatches(patches)
// TODO(olafur) optimize, this is SUPER inefficient
patches
- .foldLeft(input) {
- case (s, p) => p.runOn(s)
+ .foldLeft(input) { case (s, p) =>
+ p.runOn(s)
}
.map(_.syntax)
.mkString("")
}
+
}
diff --git a/instrumentation/src/test/scala/com.olegych.scastie.instrumentation/Diff.scala b/instrumentation/src/test/scala/com.olegych.scastie.instrumentation/Diff.scala
index 3409a685d..50aab123f 100644
--- a/instrumentation/src/test/scala/com.olegych.scastie.instrumentation/Diff.scala
+++ b/instrumentation/src/test/scala/com.olegych.scastie.instrumentation/Diff.scala
@@ -3,9 +3,10 @@ package com.olegych.scastie.instrumentation
import com.olegych.scastie.util.ScastieFileUtil
case class DiffFailure(title: String, expected: String, obtained: String, diff: String)
- extends Exception(title + "\n" + Diff.error2message(obtained, expected))
+ extends Exception(title + "\n" + Diff.error2message(obtained, expected))
object Diff {
+
def error2message(obtained: String, expected: String): String = {
ScastieFileUtil.write(new java.io.File("target/obtained.scala").toPath, obtained, truncate = true)
val sb = new StringBuilder
@@ -13,18 +14,18 @@ object Diff {
sb.append("\n")
sb.append(s"""
- ## Obtained
- #${trailingSpace(obtained)}
+ ## Obtained
+ #${trailingSpace(obtained)}
""".stripMargin('#'))
sb.append(s"""
- ## Expected
- #${trailingSpace(expected)}
+ ## Expected
+ #${trailingSpace(expected)}
""".stripMargin('#'))
sb.append(s"""
- ## Diff
- #${trailingSpace(compareContents(obtained, expected))}
+ ## Diff
+ #${trailingSpace(compareContents(obtained, expected))}
""".stripMargin('#'))
sb.toString()
}
@@ -41,7 +42,7 @@ object Diff {
def compareContents(obtained: String, expected: String): String = {
compareContents(
expected = expected.replace("\r\n", "\n").trim.split("\n").toList,
- obtained = obtained.replace("\r\n", "\n").trim.split("\n").toList,
+ obtained = obtained.replace("\r\n", "\n").trim.split("\n").toList
)
}
@@ -49,16 +50,16 @@ object Diff {
import scala.jdk.CollectionConverters._
val diff = difflib.DiffUtils.diff(expected.asJava, obtained.asJava)
if (diff.getDeltas.isEmpty) ""
- else
- difflib.DiffUtils
- .generateUnifiedDiff(
- "expected",
- "obtained",
- expected.asJava,
- diff,
- 1
- )
- .asScala
- .mkString("\n")
+ else difflib.DiffUtils
+ .generateUnifiedDiff(
+ "expected",
+ "obtained",
+ expected.asJava,
+ diff,
+ 1
+ )
+ .asScala
+ .mkString("\n")
}
+
}
diff --git a/instrumentation/src/test/scala/com.olegych.scastie.instrumentation/InstrumentSpecs.scala b/instrumentation/src/test/scala/com.olegych.scastie.instrumentation/InstrumentSpecs.scala
index c2b4ceed2..cb192c5b4 100644
--- a/instrumentation/src/test/scala/com.olegych.scastie.instrumentation/InstrumentSpecs.scala
+++ b/instrumentation/src/test/scala/com.olegych.scastie.instrumentation/InstrumentSpecs.scala
@@ -1,21 +1,21 @@
package com.olegych.scastie
package instrumentation
+import java.nio.file._
+import scala.jdk.CollectionConverters._
+
import com.olegych.scastie.api.ScalaTarget
import com.olegych.scastie.util.ScastieFileUtil.slurp
import org.scalatest.funsuite.AnyFunSuite
-import java.nio.file._
-import scala.jdk.CollectionConverters._
-
class InstrumentSpecs extends AnyFunSuite {
import InstrumentationFailure._
private val testFiles = {
val path = Paths.get("instrumentation", "src", "test", "resources")
- val s = Files.newDirectoryStream(path)
- val t = s.asScala.toList.filter(_.endsWith(".scala"))
+ val s = Files.newDirectoryStream(path)
+ val t = s.asScala.toList.filter(_.endsWith(".scala"))
s.close()
t
}
@@ -50,13 +50,11 @@ class InstrumentSpecs extends AnyFunSuite {
}
test("extends App trait fails") {
- val Left(HasMainMethod) =
- Instrument("object Main extends App { }", ScalaTarget.Jvm.default)
+ val Left(HasMainMethod) = Instrument("object Main extends App { }", ScalaTarget.Jvm.default)
}
test("with App trait fails") {
- val Left(HasMainMethod) =
- Instrument("trait Foo; object Main extends Foo with App { }", ScalaTarget.Jvm.default)
+ val Left(HasMainMethod) = Instrument("trait Foo; object Main extends Foo with App { }", ScalaTarget.Jvm.default)
}
test("extends App primary fails") {
diff --git a/project/CopyRecursively.scala b/project/CopyRecursively.scala
index 837d0c473..120301a32 100644
--- a/project/CopyRecursively.scala
+++ b/project/CopyRecursively.scala
@@ -1,7 +1,8 @@
-import java.nio.file.{Path, Files, SimpleFileVisitor, FileVisitResult}
+import java.nio.file.{FileVisitResult, Files, Path, SimpleFileVisitor}
import java.nio.file.attribute.BasicFileAttributes
object CopyRecursively {
+
def apply(source: Path, destination: Path, directoryFilter: (Path, Int) => Boolean): Unit = {
Files.walkFileTree(
@@ -9,20 +10,21 @@ object CopyRecursively {
new CopyVisitor(source, destination, directoryFilter)
)
}
+
}
-class CopyVisitor(source: Path, destination: Path, directoryFilter: (Path, Int) => Boolean) extends SimpleFileVisitor[Path] {
+class CopyVisitor(source: Path, destination: Path, directoryFilter: (Path, Int) => Boolean)
+ extends SimpleFileVisitor[Path] {
- private def relative(subPath: Path): Path =
- destination.resolve(source.relativize(subPath))
+ private def relative(subPath: Path): Path = destination.resolve(source.relativize(subPath))
private def pathDepth(dir: Path): Int = {
dir.getNameCount - source.getNameCount - 1
}
override def preVisitDirectory(
- dir: Path,
- attrs: BasicFileAttributes
+ dir: Path,
+ attrs: BasicFileAttributes
): FileVisitResult = {
def copy(): FileVisitResult = {
@@ -43,4 +45,5 @@ class CopyVisitor(source: Path, destination: Path, directoryFilter: (Path, Int)
Files.copy(file, relative(file))
FileVisitResult.CONTINUE
}
+
}
diff --git a/project/Deployment.scala b/project/Deployment.scala
index 23a276229..7f1794f71 100644
--- a/project/Deployment.scala
+++ b/project/Deployment.scala
@@ -1,113 +1,121 @@
-import sbt._
-import Keys._
-
-import SbtShared.gitHashNow
-
import java.io.File
import java.nio.file._
import java.nio.file.attribute._
import java.nio.file.StandardCopyOption.REPLACE_EXISTING
+import java.time.LocalDateTime
+import com.typesafe
import com.typesafe.config.ConfigFactory
import com.typesafe.sbt.SbtNativePackager.Universal
+import sbt._
import sbtdocker.DockerKeys.{docker, dockerBuildAndPush, imageNames}
import sbtdocker.ImageName
import sys.process._
-import java.time.LocalDateTime
-import com.typesafe
+import Keys._
+import SbtShared.gitHashNow
object Deployment {
+
def settings(server: Project, sbtRunner: Project, metalsRunner: Project): Seq[Def.Setting[Task[Unit]]] = Seq(
- deploy := deployTask(server, sbtRunner, metalsRunner, Production).value,
- deployStaging := deployTask(server, sbtRunner, metalsRunner, Staging).value,
- publishContainers := publishContainers(sbtRunner, metalsRunner).value,
+ deploy := deployTask(server, sbtRunner, metalsRunner, Production).value,
+ deployStaging := deployTask(server, sbtRunner, metalsRunner, Staging).value,
+ publishContainers := publishContainers(sbtRunner, metalsRunner).value,
generateDeploymentScripts := generateDeploymentScriptsTask(server, sbtRunner, metalsRunner).value,
- deployLocal := deployLocalTask(server, sbtRunner, metalsRunner).value
+ deployLocal := deployLocalTask(server, sbtRunner, metalsRunner).value
)
- lazy val deploy = taskKey[Unit]("Deploy server and sbt instances")
- lazy val deployStaging = taskKey[Unit]("Deploy server and sbt instances with staging configuration")
- lazy val publishContainers = taskKey[Unit]("Publishes sbt runners and metals runner to docker repository")
+ lazy val deploy = taskKey[Unit]("Deploy server and sbt instances")
+ lazy val deployStaging = taskKey[Unit]("Deploy server and sbt instances with staging configuration")
+ lazy val publishContainers = taskKey[Unit]("Publishes sbt runners and metals runner to docker repository")
lazy val generateDeploymentScripts = taskKey[Unit]("Generates deployment scripts with production configuration.")
- lazy val deployLocal = taskKey[Unit]("Deploy locally")
-
- def deployTask(server: Project, sbtRunner: Project, metalsRunner: Project, deploymentType: DeploymentType): Def.Initialize[Task[Unit]] =
- Def.task {
- val deployment = deploymentTask(sbtRunner, metalsRunner, deploymentType).value
- val serverZip = (server / Universal / packageBin).value.toPath
-
- deployment.deploy(serverZip)
- }
-
- def publishContainers(sbtRunner: Project, metalsRunner: Project): Def.Initialize[Task[Unit]] =
- Def.task {
- (sbtRunner / dockerBuildAndPush).value
- (metalsRunner / dockerBuildAndPush).value
- }
+ lazy val deployLocal = taskKey[Unit]("Deploy locally")
+
+ def deployTask(
+ server: Project,
+ sbtRunner: Project,
+ metalsRunner: Project,
+ deploymentType: DeploymentType
+ ): Def.Initialize[Task[Unit]] = Def.task {
+ val deployment = deploymentTask(sbtRunner, metalsRunner, deploymentType).value
+ val serverZip = (server / Universal / packageBin).value.toPath
+
+ deployment.deploy(serverZip)
+ }
- def generateDeploymentScriptsTask(server: Project, sbtRunner: Project, metalsRunner: Project): Def.Initialize[Task[Unit]] =
- Def.task {
- val deployment = deploymentTask(sbtRunner, metalsRunner, Production).value
- deployment.generateDeploymentScripts()
- }
+ def publishContainers(sbtRunner: Project, metalsRunner: Project): Def.Initialize[Task[Unit]] = Def.task {
+ (sbtRunner / dockerBuildAndPush).value
+ (metalsRunner / dockerBuildAndPush).value
+ }
+ def generateDeploymentScriptsTask(
+ server: Project,
+ sbtRunner: Project,
+ metalsRunner: Project
+ ): Def.Initialize[Task[Unit]] = Def.task {
+ val deployment = deploymentTask(sbtRunner, metalsRunner, Production).value
+ deployment.generateDeploymentScripts()
+ }
def deployLocalTask(server: Project, sbtRunner: Project, metalsRunner: Project): Def.Initialize[Task[Unit]] =
Def.task {
val deployment = deploymentTask(sbtRunner, metalsRunner, Local).value
- val serverZip = (server / Universal / packageBin).value.toPath
+ val serverZip = (server / Universal / packageBin).value.toPath
(sbtRunner / docker).value
(metalsRunner / docker).value
deployment.deployLocal(serverZip)
}
- private def deploymentTask(sbtRunner: Project, metalsRunner: Project, deploymentType: DeploymentType): Def.Initialize[Task[Deployment]] =
- Def.task {
- new Deployment(
- rootFolder = (ThisBuild / baseDirectory).value,
- version = version.value,
- sbtDockerImage = (sbtRunner / docker / imageNames).value.head,
- metalsDockerImage = (metalsRunner / docker / imageNames).value.head,
- deploymentType = deploymentType,
- logger = streams.value.log
- )
- }
+ private def deploymentTask(
+ sbtRunner: Project,
+ metalsRunner: Project,
+ deploymentType: DeploymentType
+ ): Def.Initialize[Task[Deployment]] = Def.task {
+ new Deployment(
+ rootFolder = (ThisBuild / baseDirectory).value,
+ version = version.value,
+ sbtDockerImage = (sbtRunner / docker / imageNames).value.head,
+ metalsDockerImage = (metalsRunner / docker / imageNames).value.head,
+ deploymentType = deploymentType,
+ logger = streams.value.log
+ )
+ }
+
}
sealed trait DeploymentType
-case object Local extends DeploymentType
-case object Staging extends DeploymentType
+case object Local extends DeploymentType
+case object Staging extends DeploymentType
case object Production extends DeploymentType
class ScastieConfig(val configurationFile: File) {
- val config = ConfigFactory.parseFile(configurationFile)
+ val config = ConfigFactory.parseFile(configurationFile)
val userName = "scastie"
- val serverConfig = config.getConfig("com.olegych.scastie.web")
+ val serverConfig = config.getConfig("com.olegych.scastie.web")
val serverHostname = serverConfig.getString("hostname")
val serverAkkaPort = serverConfig.getInt("akka-port")
- val metalsConfig = config.getConfig("scastie.metals")
- val metalsPort = metalsConfig.getInt("port")
+ val metalsConfig = config.getConfig("scastie.metals")
+ val metalsPort = metalsConfig.getInt("port")
val cacheExpireInSeconds = metalsConfig.getInt("cache-expire-in-seconds")
- val balancerConfig = config.getConfig("com.olegych.scastie.balancer")
- val runnersHostname = balancerConfig.getString("remote-hostname")
+ val balancerConfig = config.getConfig("com.olegych.scastie.balancer")
+ val runnersHostname = balancerConfig.getString("remote-hostname")
val sbtRunnersPortsStart = balancerConfig.getInt("remote-sbt-ports-start")
- val containerType = balancerConfig.getString("snippets-storage")
+ val containerType = balancerConfig.getString("snippets-storage")
private val sbtRunnersPortsSize = balancerConfig.getInt("remote-sbt-ports-size")
- val sbtRunnersPortsEnd = sbtRunnersPortsStart + sbtRunnersPortsSize - 1
+ val sbtRunnersPortsEnd = sbtRunnersPortsStart + sbtRunnersPortsSize - 1
}
object ScastieConfig {
- def ofType(deploymentType: DeploymentType, deploymentFolder: File): ScastieConfig =
- deploymentType match {
- case Local => new ScastieConfig(deploymentFolder / "local.conf")
- case Staging => new ScastieConfig(deploymentFolder / "staging.conf")
- case Production => new ScastieConfig(deploymentFolder / "production.conf")
- }
+
+ def ofType(deploymentType: DeploymentType, deploymentFolder: File): ScastieConfig = deploymentType match {
+ case Local => new ScastieConfig(deploymentFolder / "local.conf")
+ case Staging => new ScastieConfig(deploymentFolder / "staging.conf")
+ case Production => new ScastieConfig(deploymentFolder / "production.conf")
+ }
def logbackConfigPath(deploymentFolder: File): Path = (deploymentFolder / "logback.xml").toPath
@@ -122,16 +130,16 @@ class Deployment(
val logger: Logger
) {
val deploymentFolder = rootFolder / "deployment"
- val config = ScastieConfig.ofType(deploymentType, deploymentFolder)
+ val config = ScastieConfig.ofType(deploymentType, deploymentFolder)
- val sbtDockerNamespace = sbtDockerImage.namespace.get
+ val sbtDockerNamespace = sbtDockerImage.namespace.get
val sbtDockerRepository = sbtDockerImage.repository
- val metalsDockerNamespace = metalsDockerImage.namespace.get
+ val metalsDockerNamespace = metalsDockerImage.namespace.get
val metalsDockerRepository = metalsDockerImage.repository
def deploy(serverZip: Path) = {
- val time = LocalDateTime.now()
+ val time = LocalDateTime.now()
val outputPath = deploymentFolder.toPath.resolve(s"generated-scripts-$time")
if (!Files.exists(outputPath)) {
@@ -139,8 +147,7 @@ class Deployment(
}
// the deployment will be sequential so if anything fails, other services will keep working.
- val success =
- createAndVerifyDeploymentScriptsData(outputPath) &&
+ val success = createAndVerifyDeploymentScriptsData(outputPath) &&
deployRunners() &&
deployMetalsRunner() &&
deployServer(serverZip)
@@ -150,13 +157,13 @@ class Deployment(
/* Create runner script which will be used during deployment to start SBT runners docker containers */
def createRunnersStartupScript(scriptOutputDirectory: Path): Path = {
- val fileName = if (deploymentType == Staging) "start-runners-staging.sh" else "start-runners.sh"
+ val fileName = if (deploymentType == Staging) "start-runners-staging.sh" else "start-runners.sh"
val scriptPath = scriptOutputDirectory.resolve(fileName)
val dockerImagePath = deploymentType match {
- case Local => s"$sbtDockerNamespace/$sbtDockerRepository:$gitHashNow"
+ case Local => s"$sbtDockerNamespace/$sbtDockerRepository:$gitHashNow"
case Production => s"$sbtDockerNamespace/$sbtDockerRepository:latest"
- case Staging => s"$sbtDockerNamespace/$sbtDockerRepository:latest"
+ case Staging => s"$sbtDockerNamespace/$sbtDockerRepository:latest"
}
val containerName = if (deploymentType == Staging) "scastie-sbt-runner-staging" else "scastie-sbt-runner"
@@ -180,7 +187,6 @@ class Deployment(
| $dockerImagePath
|done""".stripMargin
-
Files.write(scriptPath, runnersStartupScriptContent.getBytes())
setPosixFilePermissions(scriptPath, executablePermissions)
@@ -189,29 +195,28 @@ class Deployment(
/* Create metals script which will be used during deployment to start metals docker container */
def createMetalsStartupScript(scriptOutputDirectory: Path): Path = {
- val fileName = if (deploymentType == Staging) "start-metals-staging.sh" else "start-metals.sh"
+ val fileName = if (deploymentType == Staging) "start-metals-staging.sh" else "start-metals.sh"
val scriptPath = scriptOutputDirectory.resolve(fileName)
val dockerImagePath = deploymentType match {
- case Local => s"$metalsDockerNamespace/$metalsDockerRepository:$gitHashNow"
+ case Local => s"$metalsDockerNamespace/$metalsDockerRepository:$gitHashNow"
case Production => s"$metalsDockerNamespace/$metalsDockerRepository:latest"
- case Staging => s"$metalsDockerNamespace/$metalsDockerRepository:latest"
+ case Staging => s"$metalsDockerNamespace/$metalsDockerRepository:latest"
}
val containerName = if (deploymentType == Staging) "scastie-metals-runner-staging" else "scastie-metals-runner"
- val metalsRunnerStartupScriptContent: String =
- s"""#!/usr/bin/env bash
- |echo "Starting Metals: Port ${config.metalsPort}"
- |docker run \\
- | --restart=always \\
- | --name=$containerName \\
- | -p ${config.metalsPort}:${config.metalsPort} \\
- | -d \\
- | -e PORT=${config.metalsPort} \\
- | -e CACHE_EXPIRE_IN_SECONDS=${config.cacheExpireInSeconds} \\
- | -e IS_DOCKER=true \\
- | $dockerImagePath""".stripMargin
+ val metalsRunnerStartupScriptContent: String = s"""#!/usr/bin/env bash
+ |echo "Starting Metals: Port ${config.metalsPort}"
+ |docker run \\
+ | --restart=always \\
+ | --name=$containerName \\
+ | -p ${config.metalsPort}:${config.metalsPort} \\
+ | -d \\
+ | -e PORT=${config.metalsPort} \\
+ | -e CACHE_EXPIRE_IN_SECONDS=${config.cacheExpireInSeconds} \\
+ | -e IS_DOCKER=true \\
+ | $dockerImagePath""".stripMargin
Files.write(scriptPath, metalsRunnerStartupScriptContent.getBytes())
setPosixFilePermissions(scriptPath, executablePermissions)
@@ -221,15 +226,15 @@ class Deployment(
/* Compares script with its remote version */
def compareScriptWithRemote(scriptPath: Path): Boolean = {
- val uri = s"${config.userName}@${config.runnersHostname}"
+ val uri = s"${config.userName}@${config.runnersHostname}"
val remoteScriptPath = scriptPath.getFileName().toString()
- val exitCode = Process(s"ssh $uri cat $remoteScriptPath") #| (s"diff - $scriptPath") ! logger
+ val exitCode = Process(s"ssh $uri cat $remoteScriptPath") #| (s"diff - $scriptPath") ! logger
logger.info(s"EXIT CODE $exitCode")
exitCode == 0
}
def generateDeploymentScripts() = {
- val time = LocalDateTime.now()
+ val time = LocalDateTime.now()
val outputPath = deploymentFolder.toPath.resolve(s"generated-scripts-$time")
if (!Files.exists(outputPath)) {
@@ -240,7 +245,6 @@ class Deployment(
createMetalsStartupScript(outputPath)
}
-
/*
* Verifies if deployment scripts are up to date on remote
* We don't want to automatically sync files between servers, as if it is misconfigured
@@ -250,33 +254,37 @@ class Deployment(
* By manually copying the files, we are ensuring that everything is properly configured.
*/
def createAndVerifyDeploymentScriptsData(scriptOutputDirectory: Path): Boolean = {
- val deploymentScript : Path = (deploymentFolder / "deploy.sh").toPath
+ val deploymentScript: Path = (deploymentFolder / "deploy.sh").toPath
val runnerContainersStartupScript: Path = createRunnersStartupScript(scriptOutputDirectory)
- val metalsContainerStartupScript: Path = createMetalsStartupScript(scriptOutputDirectory)
-
- List(deploymentScript, runnerContainersStartupScript, metalsContainerStartupScript).map { script =>
- val isUpToDate: Boolean = compareScriptWithRemote(script)
- if (!isUpToDate) {
- val remoteScriptPath = script.getFileName().toString()
- logger.error(s"Deployment stopped. Script: $script is not up to date with remote version $remoteScriptPath or could not be validated. You have to update it manually. It should be located in the user home directory.")
+ val metalsContainerStartupScript: Path = createMetalsStartupScript(scriptOutputDirectory)
+
+ List(deploymentScript, runnerContainersStartupScript, metalsContainerStartupScript)
+ .map { script =>
+ val isUpToDate: Boolean = compareScriptWithRemote(script)
+ if (!isUpToDate) {
+ val remoteScriptPath = script.getFileName().toString()
+ logger.error(
+ s"Deployment stopped. Script: $script is not up to date with remote version $remoteScriptPath or could not be validated. You have to update it manually. It should be located in the user home directory."
+ )
+ }
+ isUpToDate
}
- isUpToDate
- }.forall(_ == true)
+ .forall(_ == true)
}
def deployRunners(): Boolean = {
- val uri = s"${config.userName}@${config.runnersHostname}"
+ val uri = s"${config.userName}@${config.runnersHostname}"
val exitCode = Process(s"ssh $uri ./deploy.sh SBT $deploymentType") ! logger
exitCode == 0
}
def deployMetalsRunner(): Boolean = {
- val uri = s"${config.userName}@${config.runnersHostname}"
+ val uri = s"${config.userName}@${config.runnersHostname}"
val exitCode = Process(s"ssh $uri ./deploy.sh Metals $deploymentType") ! logger
exitCode == 0
}
- //#################################################################################################################//
+ // #################################################################################################################//
def deployLocal(serverZip: Path): Unit = {
val destination = rootFolder.toPath.resolve("local")
@@ -296,10 +304,9 @@ class Deployment(
val deploymentFiles = deployServerFiles(serverZip, destination, local = true).files
- deploymentFiles.foreach(
- file =>
- Files
- .copy(file, destination.resolve(file.getFileName), REPLACE_EXISTING)
+ deploymentFiles.foreach(file =>
+ Files
+ .copy(file, destination.resolve(file.getFileName), REPLACE_EXISTING)
)
logger.success("Local deployment script are in ./local directory.")
@@ -308,30 +315,31 @@ class Deployment(
def deployServer(serverZip: Path): Boolean = {
val serverScriptDir = Files.createTempDirectory("server")
- val deploymentFiles =
- deployServerFiles(serverZip, serverScriptDir, local = false)
+ val deploymentFiles = deployServerFiles(serverZip, serverScriptDir, local = false)
deploymentFiles.files.foreach(rsyncServer)
val scriptFileName = deploymentFiles.serverScript.getFileName
- val uri = config.userName + "@" + config.serverHostname
- val exitCode = Process(s"ssh $uri ./$scriptFileName") ! logger
+ val uri = config.userName + "@" + config.serverHostname
+ val exitCode = Process(s"ssh $uri ./$scriptFileName") ! logger
exitCode == 0
}
case class DeploymentFiles(
- serverZip: Path,
- serverScript: Path,
- productionConfig: Path,
- logbackConfig: Path,
- secretConfig: Option[Path] = None
+ serverZip: Path,
+ serverScript: Path,
+ productionConfig: Path,
+ logbackConfig: Path,
+ secretConfig: Option[Path] = None
) {
+
def files: List[Path] = List(
serverZip,
serverScript,
productionConfig,
logbackConfig
) ++ secretConfig.toList
+
}
private def deployServerFiles(serverZip: Path, destination: Path, local: Boolean): DeploymentFiles = {
@@ -339,10 +347,10 @@ class Deployment(
val serverScript = destination.resolve("server.sh")
- val configFileName = config.configurationFile.toPath().getFileName
- val logbackConfig = ScastieConfig.logbackConfigPath(deploymentFolder)
- val logbackConfigFileName= logbackConfig.getFileName()
- val serverZipFileName = serverZip.getFileName.toString.replace(".zip", "")
+ val configFileName = config.configurationFile.toPath().getFileName
+ val logbackConfig = ScastieConfig.logbackConfigPath(deploymentFolder)
+ val logbackConfigFileName = logbackConfig.getFileName()
+ val serverZipFileName = serverZip.getFileName.toString.replace(".zip", "")
val regex = "^[a-zA-Z0-9]+$".r
if (regex.findFirstIn(config.userName).isEmpty)
@@ -352,34 +360,33 @@ class Deployment(
if (!local) s"/home/${config.userName}/"
else ""
- val isMongoDB = config.containerType == "mongo"
+ val isMongoDB = config.containerType == "mongo"
val mongodbConfig = if (deploymentType == Production) "mongodb-prod.conf" else "mongodb-staging.conf"
- val content =
- s"""|#!/usr/bin/env bash
- |
- |whoami
- |
- |if [ -e ${baseDir}RUNNING_PID ]; then
- | kill -9 `cat ${baseDir}RUNNING_PID`
- |fi
- |
- |if [ ! -f ${baseDir}${mongodbConfig} ] && ${isMongoDB}; then
- | echo "mongodb configuration file: ${baseDir}${mongodbConfig} is missing"
- | exit 1
- |fi
- |
- |rm -rf ${baseDir}server/*
- |unzip -o -d ${baseDir}server ${baseDir}$serverZipFileName
- |mv ${baseDir}server/$serverZipFileName/* ${baseDir}server/
- |rm -rf ${baseDir}server/$serverZipFileName
- |
- |nohup ${baseDir}server/bin/server \\
- | -J-Xmx1G \\
- | -Dconfig.file=${baseDir}${configFileName} \\
- | -Dlogback.configurationFile=${baseDir}${logbackConfigFileName} \\
- | &>/dev/null &
- |""".stripMargin
+ val content = s"""|#!/usr/bin/env bash
+ |
+ |whoami
+ |
+ |if [ -e ${baseDir}RUNNING_PID ]; then
+ | kill -9 `cat ${baseDir}RUNNING_PID`
+ |fi
+ |
+ |if [ ! -f ${baseDir}${mongodbConfig} ] && ${isMongoDB}; then
+ | echo "mongodb configuration file: ${baseDir}${mongodbConfig} is missing"
+ | exit 1
+ |fi
+ |
+ |rm -rf ${baseDir}server/*
+ |unzip -o -d ${baseDir}server ${baseDir}$serverZipFileName
+ |mv ${baseDir}server/$serverZipFileName/* ${baseDir}server/
+ |rm -rf ${baseDir}server/$serverZipFileName
+ |
+ |nohup ${baseDir}server/bin/server \\
+ | -J-Xmx1G \\
+ | -Dconfig.file=${baseDir}${configFileName} \\
+ | -Dlogback.configurationFile=${baseDir}${logbackConfigFileName} \\
+ | &>/dev/null &
+ |""".stripMargin
Files.write(serverScript, content.getBytes)
setPosixFilePermissions(serverScript, executablePermissions)
@@ -391,25 +398,25 @@ class Deployment(
serverScript,
config.configurationFile.toPath(),
logbackConfig,
- None,
+ None
)
}
-
private def rsync(file: Path, userName: String, hostname: String, logger: Logger): Unit = {
- val uri = userName + "@" + hostname
+ val uri = userName + "@" + hostname
val fileName = file.getFileName
Process(s"rsync $file $uri:$fileName") ! logger
}
- private def rsyncServer(file: Path) =
- rsync(file, config.userName, config.serverHostname, logger)
+ private def rsyncServer(file: Path) = rsync(file, config.userName, config.serverHostname, logger)
private val executablePermissions = PosixFilePermissions.fromString("rwxr-xr-x")
+
private def setPosixFilePermissions(path: Path, perms: java.util.Set[PosixFilePermission]): Path = {
try Files.setPosixFilePermissions(path, perms)
catch {
case e: Exception => path
}
}
+
}
diff --git a/project/DockerHelper.scala b/project/DockerHelper.scala
index 423b7549d..7ca22a2c1 100644
--- a/project/DockerHelper.scala
+++ b/project/DockerHelper.scala
@@ -1,7 +1,7 @@
-import sbtdocker.DockerPlugin.autoImport._
-
import java.nio.file.Path
+import sbtdocker.DockerPlugin.autoImport._
+
object DockerHelper {
val alpineImageName = "alpine:3.17"
@@ -36,10 +36,10 @@ object DockerHelper {
env("LANG", "en_US.UTF-8")
env("HOME", userHome)
- val artifactName = artifactZip.getFileName.toString.replace(".zip", "")
+ val artifactName = artifactZip.getFileName.toString.replace(".zip", "")
val artifactZipFileName = artifactZip.getFileName.toString
- val artifactTargetPath = s"/app/$artifactZipFileName"
- val configDestination = "/home/scastie/config.conf"
+ val artifactTargetPath = s"/app/$artifactZipFileName"
+ val configDestination = "/home/scastie/config.conf"
add(configPath.toFile, configDestination)
add(artifactZip.toFile, artifactTargetPath)
@@ -55,17 +55,18 @@ object DockerHelper {
}
}
-
- def apply(baseDirectory: Path,
- sbtTargetDir: Path,
- sbtScastie: String,
- ivyHome: Path,
- organization: String,
- artifact: Path,
- sbtVersion: String): Dockerfile = {
+ def apply(
+ baseDirectory: Path,
+ sbtTargetDir: Path,
+ sbtScastie: String,
+ ivyHome: Path,
+ organization: String,
+ artifact: Path,
+ sbtVersion: String
+ ): Dockerfile = {
val artifactTargetPath = s"/app/${artifact.getFileName()}"
- val generatedProjects = new GenerateProjects(sbtTargetDir)
+ val generatedProjects = new GenerateProjects(sbtTargetDir)
generatedProjects.generateSbtProjects()
val logbackConfDestination = "/home/scastie/logback.xml"
@@ -86,7 +87,7 @@ object DockerHelper {
destination = ivyLocalTemp,
directoryFilter = { (dir, depth) =>
lazy val isSbtScastiePath = dir.getName(0).toString == sbtScastie
- lazy val dirName = dir.getFileName.toString
+ lazy val dirName = dir.getFileName.toString
if (depth == 1) {
dirName == SbtShared.versionNow || dirName == SbtShared.versionRuntime || isSbtScastiePath
@@ -170,4 +171,5 @@ object DockerHelper {
)
}
}
+
}
diff --git a/project/GenerateProjects.scala b/project/GenerateProjects.scala
index 751b75765..4e85b1c7b 100644
--- a/project/GenerateProjects.scala
+++ b/project/GenerateProjects.scala
@@ -17,7 +17,7 @@ class GenerateProjects(sbtTargetDir: Path) {
)
def scala(version: String): Inputs = defaultWithMain.copy(
- target = ScalaTarget.Jvm(version),
+ target = ScalaTarget.Jvm(version)
)
val scala212 = scala(BuildInfo.latest212)
@@ -29,7 +29,7 @@ class GenerateProjects(sbtTargetDir: Path) {
val scalaJs = Inputs.default.copy(
code = """@_root_.scala.scalajs.js.annotation.JSExportTopLevel("ScastiePlaygroundMain") class Test""".stripMargin,
- target = ScalaTarget.Js.default,
+ target = ScalaTarget.Js.default
)
List(
@@ -37,24 +37,22 @@ class GenerateProjects(sbtTargetDir: Path) {
(scala213, "scala213"),
(dotty, "dotty"),
(scalaJs, "scalaJs")
- ).map {
- case (inputs, name) =>
- new GeneratedProject(
- inputs,
- projectTarget.resolve(name)
- )
+ ).map { case (inputs, name) =>
+ new GeneratedProject(
+ inputs,
+ projectTarget.resolve(name)
+ )
}
}
- def generateSbtProjects(): Unit =
- projects.foreach(_.generateSbtProject)
+ def generateSbtProjects(): Unit = projects.foreach(_.generateSbtProject)
}
class GeneratedProject(inputs: Inputs, sbtDir: Path) {
- private val buildFile = sbtDir.resolve("build.sbt")
+ private val buildFile = sbtDir.resolve("build.sbt")
private val projectDir = sbtDir.resolve("project")
private val pluginFile = projectDir.resolve("plugins.sbt")
- private val codeFile = sbtDir.resolve("src/main/scala/main.scala")
+ private val codeFile = sbtDir.resolve("src/main/scala/main.scala")
def generateSbtProject(): Unit = {
Files.createDirectories(projectDir)
@@ -76,4 +74,5 @@ class GeneratedProject(inputs: Inputs, sbtDir: Path) {
s"""cd $dest/$dir && sbt "${inputs.target.sbtRunCommand(true)}""""
}
+
}
diff --git a/runtime-scala/src/main/scala/com.olegych.scastie.api.runtime/SharedRuntime.scala b/runtime-scala/src/main/scala/com.olegych.scastie.api.runtime/SharedRuntime.scala
index c4f5b2ad9..4ddd53d37 100644
--- a/runtime-scala/src/main/scala/com.olegych.scastie.api.runtime/SharedRuntime.scala
+++ b/runtime-scala/src/main/scala/com.olegych.scastie.api.runtime/SharedRuntime.scala
@@ -4,6 +4,7 @@ package runtime
import play.api.libs.json.Json
protected[runtime] trait SharedRuntime {
+
def write(instrumentations: List[Instrumentation]): String = {
if (instrumentations.isEmpty) "" else Json.stringify(Json.toJson(instrumentations))
}
@@ -26,4 +27,5 @@ protected[runtime] trait SharedRuntime {
Value(out, typeName.replace(Instrumentation.instrumentedObject + ".", ""))
}
}
+
}
diff --git a/runtime-scala/src/main/scala/com.olegych.scastie.api.runtime/package.scala b/runtime-scala/src/main/scala/com.olegych.scastie.api.runtime/package.scala
index 18520cf15..6d46791cf 100644
--- a/runtime-scala/src/main/scala/com.olegych.scastie.api.runtime/package.scala
+++ b/runtime-scala/src/main/scala/com.olegych.scastie.api.runtime/package.scala
@@ -7,11 +7,11 @@ package object runtime {
val Html: api.Html.type = api.Html
- def image(path: String): Html = Runtime.image(path)
+ def image(path: String): Html = Runtime.image(path)
def toBase64(in: BufferedImage): Html = Runtime.toBase64(in)
implicit class HtmlHelper(val sc: StringContext) extends AnyVal {
- def html(args: Any*) = Html(sc.s(args: _*))
+ def html(args: Any*) = Html(sc.s(args: _*))
def htmlRaw(args: Any*) = Html(sc.raw(args: _*))
}
diff --git a/runtime-scala/src/main/scalajs/com.olegych.scastie.api.runtime/DomHook.scala b/runtime-scala/src/main/scalajs/com.olegych.scastie.api.runtime/DomHook.scala
index 611db58bf..b9294cfb1 100644
--- a/runtime-scala/src/main/scalajs/com.olegych.scastie.api.runtime/DomHook.scala
+++ b/runtime-scala/src/main/scalajs/com.olegych.scastie.api.runtime/DomHook.scala
@@ -1,12 +1,11 @@
package com.olegych.scastie.api
package runtime
-import org.scalajs.dom.HTMLElement
-import scala.scalajs.js
-
+import java.util.UUID
import scala.collection.mutable.Buffer
+import scala.scalajs.js
-import java.util.UUID
+import org.scalajs.dom.HTMLElement
trait DomHook {
private val elements = Buffer.empty[HTMLElement]
diff --git a/runtime-scala/src/main/scalajs/com.olegych.scastie.api.runtime/Runtime.scala b/runtime-scala/src/main/scalajs/com.olegych.scastie.api.runtime/Runtime.scala
index b73cb1112..74d3228a0 100644
--- a/runtime-scala/src/main/scalajs/com.olegych.scastie.api.runtime/Runtime.scala
+++ b/runtime-scala/src/main/scalajs/com.olegych.scastie.api.runtime/Runtime.scala
@@ -1,21 +1,22 @@
package com.olegych.scastie.api
package runtime
-import play.api.libs.json.Json
-
-import org.scalajs.dom.HTMLElement
-
-import java.util.UUID
-
import java.awt.image.BufferedImage
-
+import java.util.UUID
import scala.reflect.ClassTag
+import org.scalajs.dom.HTMLElement
+import play.api.libs.json.Json
+
object Runtime extends SharedRuntime {
+
def write(in: Either[Option[RuntimeError], List[Instrumentation]]): String = {
Json.stringify(Json.toJson(ScalaJsResult(in)))
}
- def render[T](a: T, attach: HTMLElement => UUID)(implicit _ct: ClassTag[T] = null): Render = {
+
+ def render[T](a: T, attach: HTMLElement => UUID)(
+ implicit _ct: ClassTag[T] = null
+ ): Render = {
val ct = Option(_ct)
a match {
case element: HTMLElement => {
@@ -26,11 +27,10 @@ object Runtime extends SharedRuntime {
}
}
- def image(path: String): Html =
- throw new Exception("image(path: String): Html works only on the jvm")
+ def image(path: String): Html = throw new Exception("image(path: String): Html works only on the jvm")
+
+ def toBase64(in: BufferedImage): Html = throw new Exception(
+ "toBase64(in: BufferedImage): Html works only on the jvm"
+ )
- def toBase64(in: BufferedImage): Html =
- throw new Exception(
- "toBase64(in: BufferedImage): Html works only on the jvm"
- )
}
diff --git a/runtime-scala/src/main/scalajvm-2/com.olegych.scastie.api.runtime/Runtime.scala b/runtime-scala/src/main/scalajvm-2/com.olegych.scastie.api.runtime/Runtime.scala
index 4476d1bc9..f70c67846 100644
--- a/runtime-scala/src/main/scalajvm-2/com.olegych.scastie.api.runtime/Runtime.scala
+++ b/runtime-scala/src/main/scalajvm-2/com.olegych.scastie.api.runtime/Runtime.scala
@@ -1,13 +1,18 @@
package com.olegych.scastie.api
package runtime
-import scala.reflect.ClassTag
import scala.reflect.runtime.universe._
+import scala.reflect.ClassTag
object Runtime extends JvmRuntime {
- def render[T](a: T)(implicit _ct: ClassTag[T] = null, _tt: TypeTag[T] = null): Render = {
+
+ def render[T](a: T)(
+ implicit _ct: ClassTag[T] = null,
+ _tt: TypeTag[T] = null
+ ): Render = {
val ct = Option(_ct)
val tt = Option(_tt)
super.render(a, tt.map(_.tpe.toString).orElse(ct.map(_.toString)).getOrElse(""))
}
+
}
diff --git a/runtime-scala/src/main/scalajvm-3/com.olegych.scastie.api.runtime/Runtime.scala b/runtime-scala/src/main/scalajvm-3/com.olegych.scastie.api.runtime/Runtime.scala
index 7431e4de0..6f0ec6e8b 100644
--- a/runtime-scala/src/main/scalajvm-3/com.olegych.scastie.api.runtime/Runtime.scala
+++ b/runtime-scala/src/main/scalajvm-3/com.olegych.scastie.api.runtime/Runtime.scala
@@ -4,9 +4,11 @@ package runtime
import scala.quoted.*
object Runtime extends JvmRuntime:
- inline def render[T](a: T): Render = ${_render('a)}
+ inline def render[T](a: T): Render = ${ _render('a) }
- private def _render[T: Type](a: Expr[T])(using Quotes): Expr[Render] =
+ private def _render[T: Type](a: Expr[T])(
+ using Quotes
+ ): Expr[Render] =
import quotes.reflect.*
val t = TypeRepr.of[T]
- '{Runtime.render($a, ${Expr(t.show)})}
+ '{ Runtime.render($a, ${ Expr(t.show) }) }
diff --git a/runtime-scala/src/main/scalajvm/com.olegych.scastie.api.runtime/Runtime.scala b/runtime-scala/src/main/scalajvm/com.olegych.scastie.api.runtime/Runtime.scala
index 540375e63..98358389f 100644
--- a/runtime-scala/src/main/scalajvm/com.olegych.scastie.api.runtime/Runtime.scala
+++ b/runtime-scala/src/main/scalajvm/com.olegych.scastie.api.runtime/Runtime.scala
@@ -1,12 +1,14 @@
package com.olegych.scastie.api
package runtime
-import javax.imageio.ImageIO
+import java.awt.image.BufferedImage
import java.io.{ByteArrayOutputStream, File}
import java.util.Base64
-import java.awt.image.BufferedImage
+
+import javax.imageio.ImageIO
protected[runtime] trait JvmRuntime extends SharedRuntime {
+
def image(path: String): Html = {
val in = ImageIO.read(new File(path))
toBase64(in)
@@ -14,8 +16,8 @@ protected[runtime] trait JvmRuntime extends SharedRuntime {
def toBase64(in: BufferedImage): Html = {
val width = in.getWidth
- val os = new ByteArrayOutputStream
- val b64 = Base64.getEncoder.wrap(os)
+ val os = new ByteArrayOutputStream
+ val b64 = Base64.getEncoder.wrap(os)
ImageIO.write(in, "png", b64)
val encoded = os.toString("UTF-8")
@@ -28,4 +30,5 @@ protected[runtime] trait JvmRuntime extends SharedRuntime {
folded = true
)
}
+
}
diff --git a/sbt-runner/src/main/scala/com.olegych.scastie.sbt/FormatActor.scala b/sbt-runner/src/main/scala/com.olegych.scastie.sbt/FormatActor.scala
index 82bef134e..1b482310d 100644
--- a/sbt-runner/src/main/scala/com.olegych.scastie.sbt/FormatActor.scala
+++ b/sbt-runner/src/main/scala/com.olegych.scastie.sbt/FormatActor.scala
@@ -5,14 +5,15 @@ import akka.actor.Actor
import com.olegych.scastie.api.FormatRequest
import com.olegych.scastie.api.FormatResponse
import com.olegych.scastie.api.ScalaTarget
-import org.scalafmt.Formatted
-import org.scalafmt.Scalafmt
+import org.scalafmt.config.NamedDialect
import org.scalafmt.config.ScalafmtConfig
import org.scalafmt.config.ScalafmtRunner
-import org.scalafmt.config.NamedDialect
+import org.scalafmt.Formatted
+import org.scalafmt.Scalafmt
import org.slf4j.LoggerFactory
object FormatActor {
+
private[sbt] def format(code: String, isWorksheetMode: Boolean, scalaTarget: ScalaTarget): Either[String, String] = {
val config: ScalafmtConfig = {
val dialect =
@@ -22,10 +23,8 @@ object FormatActor {
else NamedDialect.scala213
val runner = {
- if (isWorksheetMode && scalaTarget.hasWorksheetMode)
- ScalafmtRunner.sbt
- else
- ScalafmtRunner.default
+ if (isWorksheetMode && scalaTarget.hasWorksheetMode) ScalafmtRunner.sbt
+ else ScalafmtRunner.default
}.withDialect(dialect)
ScalafmtConfig.default.copy(runner = runner)
@@ -36,17 +35,18 @@ object FormatActor {
case Formatted.Failure(failure) => Left(failure.toString)
}
}
+
}
class FormatActor() extends Actor {
import FormatActor._
private val log = LoggerFactory.getLogger(getClass)
- override def receive: Receive = {
- case FormatRequest(code, isWorksheetMode, scalaTarget) =>
- log.info(s"format (isWorksheetMode: $isWorksheetMode)")
- log.info(code)
+ override def receive: Receive = { case FormatRequest(code, isWorksheetMode, scalaTarget) =>
+ log.info(s"format (isWorksheetMode: $isWorksheetMode)")
+ log.info(code)
- sender() ! FormatResponse(format(code, isWorksheetMode, scalaTarget))
+ sender() ! FormatResponse(format(code, isWorksheetMode, scalaTarget))
}
+
}
diff --git a/sbt-runner/src/main/scala/com.olegych.scastie.sbt/OutputExtractor.scala b/sbt-runner/src/main/scala/com.olegych.scastie.sbt/OutputExtractor.scala
index 8709802be..f0ffdce07 100644
--- a/sbt-runner/src/main/scala/com.olegych.scastie.sbt/OutputExtractor.scala
+++ b/sbt-runner/src/main/scala/com.olegych.scastie.sbt/OutputExtractor.scala
@@ -1,6 +1,7 @@
package com.olegych.scastie.sbt
import java.time.Instant
+import scala.util.control.NonFatal
import com.olegych.scastie.api._
import com.olegych.scastie.instrumentation.Instrument
@@ -8,25 +9,26 @@ import com.olegych.scastie.sbt.SbtProcess._
import org.slf4j.LoggerFactory
import play.api.libs.json._
-import scala.util.control.NonFatal
+class OutputExtractor(
+ getScalaJsContent: () => Option[String],
+ getScalaJsSourceMapContent: () => Option[String],
+ isProduction: Boolean,
+ promptUniqueId: String
+) {
-class OutputExtractor(getScalaJsContent: () => Option[String],
- getScalaJsSourceMapContent: () => Option[String],
- isProduction: Boolean,
- promptUniqueId: String) {
def extractProgress(output: ProcessOutput, sbtRun: SbtRun, isReloading: Boolean): SnippetProgress = {
import sbtRun._
- val problems = extractProblems(output.line, Instrument.getMessageLineOffset(inputs), inputs.isWorksheetMode)
+ val problems = extractProblems(output.line, Instrument.getMessageLineOffset(inputs), inputs.isWorksheetMode)
val instrumentations = extract[List[Instrumentation]](output.line)
- val runtimeError = extractRuntimeError(output.line, Instrument.getExceptionLineOffset(inputs))
- val sbtOutput = extract[ConsoleOutput.SbtOutput](output.line)
+ val runtimeError = extractRuntimeError(output.line, Instrument.getExceptionLineOffset(inputs))
+ val sbtOutput = extract[ConsoleOutput.SbtOutput](output.line)
// sbt plugin is not loaded at this stage. we need to drop those messages
val hiddenInitializationMessages = List(
"WARNING: A terminally deprecated method in java.lang.System has been called",
"WARNING: System::setSecurityManager has been called",
"WARNING: Please consider reporting this to the maintainers",
- "WARNING: System::setSecurityManager will be removed in a future release",
+ "WARNING: System::setSecurityManager will be removed in a future release"
)
val isHiddenSbtMessage =
@@ -37,14 +39,15 @@ class OutputExtractor(getScalaJsContent: () => Option[String],
val isScalaJs = inputs.target.targetType == ScalaTargetType.JS
val userOutput =
- if (problems.toList.flatten.isEmpty
- && instrumentations.toList.flatten.isEmpty
- && runtimeError.isEmpty
- && !isDone
- && !isHiddenSbtMessage
- && !isReloading
- && sbtOutput.isEmpty)
- Some(output)
+ if (
+ problems.toList.flatten.isEmpty
+ && instrumentations.toList.flatten.isEmpty
+ && runtimeError.isEmpty
+ && !isDone
+ && !isHiddenSbtMessage
+ && !isReloading
+ && sbtOutput.isEmpty
+ ) Some(output)
else None
val (scalaJsContent, scalaJsSourceMapContent) =
@@ -82,38 +85,35 @@ class OutputExtractor(getScalaJsContent: () => Option[String],
)
}
- private implicit val formatSourceMap: OFormat[SourceMap] =
- Json.format[SourceMap]
+ private implicit val formatSourceMap: OFormat[SourceMap] = Json.format[SourceMap]
private case class SourceMap(
- version: Int,
- file: String,
- mappings: String,
- sources: List[String],
- names: List[String],
- lineCount: Int
+ version: Int,
+ file: String,
+ mappings: String,
+ sources: List[String],
+ names: List[String],
+ lineCount: Int
)
private def remapSourceMap(
- snippetId: SnippetId
+ snippetId: SnippetId
)(sourceMapRaw: String): String = {
Json
.fromJson[SourceMap](Json.parse(sourceMapRaw))
.asOpt
.map { sourceMap =>
- val sourceMap0 =
- sourceMap.copy(
- sources = sourceMap.sources.map(
- source =>
- if (source.startsWith(ScalaTarget.Js.sourceUUID)) {
- val host =
- if (isProduction) "https://scastie.scala-lang.org"
- else "http://localhost:9000"
-
- host + snippetId.scalaJsUrl(ScalaTarget.Js.sourceFilename)
- } else source
- )
+ val sourceMap0 = sourceMap.copy(
+ sources = sourceMap.sources.map(source =>
+ if (source.startsWith(ScalaTarget.Js.sourceUUID)) {
+ val host =
+ if (isProduction) "https://scastie.scala-lang.org"
+ else "http://localhost:9000"
+
+ host + snippetId.scalaJsUrl(ScalaTarget.Js.sourceFilename)
+ } else source
)
+ )
Json.prettyPrint(Json.toJson(sourceMap0))
}
@@ -121,9 +121,9 @@ class OutputExtractor(getScalaJsContent: () => Option[String],
}
private def extractProblems(
- line: String,
- lineOffset: Int,
- isWorksheetMode: Boolean
+ line: String,
+ lineOffset: Int,
+ isWorksheetMode: Boolean
): Option[List[Problem]] = {
val problems = extract[List[Problem]](line)
@@ -143,7 +143,8 @@ class OutputExtractor(getScalaJsContent: () => Option[String],
private def extractRuntimeError(line: String, lineOffset: Int): Option[RuntimeError] = {
extract[RuntimeErrorWrap](line).flatMap {
_.error.map { error =>
- val noStackTraceError = if (error.message.contains("No main class detected.")) error.copy(fullStack = "") else error
+ val noStackTraceError =
+ if (error.message.contains("No main class detected.")) error.copy(fullStack = "") else error
val errorWithOffset = noStackTraceError.copy(
line = noStackTraceError.line.map(lineNumber => (lineNumber + lineOffset) max 1)
)
diff --git a/sbt-runner/src/main/scala/com.olegych.scastie.sbt/SbtActor.scala b/sbt-runner/src/main/scala/com.olegych.scastie.sbt/SbtActor.scala
index 64e875b06..93a8fc6e5 100644
--- a/sbt-runner/src/main/scala/com.olegych.scastie.sbt/SbtActor.scala
+++ b/sbt-runner/src/main/scala/com.olegych.scastie.sbt/SbtActor.scala
@@ -1,22 +1,23 @@
package com.olegych.scastie.sbt
+import scala.concurrent.duration._
+
+import akka.actor.{Actor, ActorContext, ActorLogging, ActorRef, ActorSelection, ActorSystem, Props}
import com.olegych.scastie.api._
import com.olegych.scastie.util._
-import akka.actor.{Actor, ActorContext, ActorLogging, ActorRef, ActorSelection, ActorSystem, Props}
-
-import scala.concurrent.duration._
case object SbtActorReady
-class SbtActor(system: ActorSystem,
- runTimeout: FiniteDuration,
- sbtReloadTimeout: FiniteDuration,
- isProduction: Boolean,
- readyRef: Option[ActorRef],
- override val reconnectInfo: Option[ReconnectInfo])
- extends Actor
- with ActorLogging
- with ActorReconnecting {
+class SbtActor(
+ system: ActorSystem,
+ runTimeout: FiniteDuration,
+ sbtReloadTimeout: FiniteDuration,
+ isProduction: Boolean,
+ readyRef: Option[ActorRef],
+ override val reconnectInfo: Option[ReconnectInfo]
+) extends Actor
+ with ActorLogging
+ with ActorReconnecting {
def balancer(context: ActorContext, info: ReconnectInfo): ActorSelection = {
import info._
@@ -47,21 +48,19 @@ class SbtActor(system: ActorSystem,
super.postStop()
}
- private val formatActor =
- context.actorOf(Props(new FormatActor()), name = "FormatActor")
-
- private val sbtRunner =
- context.actorOf(
- Props(
- new SbtProcess(
- runTimeout,
- sbtReloadTimeout,
- isProduction,
- javaOptions = Seq("-Xms512m", "-Xmx1g")
- )
- ),
- name = "SbtRunner"
- )
+ private val formatActor = context.actorOf(Props(new FormatActor()), name = "FormatActor")
+
+ private val sbtRunner = context.actorOf(
+ Props(
+ new SbtProcess(
+ runTimeout,
+ sbtReloadTimeout,
+ isProduction,
+ javaOptions = Seq("-Xms512m", "-Xmx1g")
+ )
+ ),
+ name = "SbtRunner"
+ )
override def receive: Receive = reconnectBehavior orElse [Any, Unit] {
case SbtPing => {
@@ -92,4 +91,5 @@ class SbtActor(system: ActorSystem,
}
}
}
+
}
diff --git a/sbt-runner/src/main/scala/com.olegych.scastie.sbt/SbtMain.scala b/sbt-runner/src/main/scala/com.olegych.scastie.sbt/SbtMain.scala
index 871972eb2..090ef0b80 100644
--- a/sbt-runner/src/main/scala/com.olegych.scastie.sbt/SbtMain.scala
+++ b/sbt-runner/src/main/scala/com.olegych.scastie.sbt/SbtMain.scala
@@ -1,18 +1,17 @@
package com.olegych.scastie.sbt
-import com.olegych.scastie.util.ScastieFileUtil.writeRunningPid
-import com.olegych.scastie.util.ReconnectInfo
+import java.util.concurrent.TimeUnit
+import scala.concurrent.duration._
+import scala.concurrent.Await
import akka.actor.{ActorSystem, Props}
+import com.olegych.scastie.util.ReconnectInfo
+import com.olegych.scastie.util.ScastieFileUtil.writeRunningPid
import com.typesafe.config.ConfigFactory
-
-import scala.concurrent.Await
-import scala.concurrent.duration._
-import java.util.concurrent.TimeUnit
-
import org.slf4j.LoggerFactory
object SbtMain {
+
def main(args: Array[String]): Unit = {
val logger = LoggerFactory.getLogger(getClass)
@@ -26,7 +25,7 @@ object SbtMain {
val config = ConfigFactory.load().getConfig("com.olegych.scastie")
val serverConfig = config.getConfig("web")
- val sbtConfig = config.getConfig("sbt")
+ val sbtConfig = config.getConfig("sbt")
val isProduction = sbtConfig.getBoolean("production")
@@ -53,13 +52,12 @@ object SbtMain {
)
}
- val reconnectInfo =
- ReconnectInfo(
- serverHostname = serverConfig.getString("hostname"),
- serverAkkaPort = serverConfig.getInt("akka-port"),
- actorHostname = sbtConfig.getString("hostname"),
- actorAkkaPort = sbtConfig.getInt("akka-port")
- )
+ val reconnectInfo = ReconnectInfo(
+ serverHostname = serverConfig.getString("hostname"),
+ serverAkkaPort = serverConfig.getInt("akka-port"),
+ actorHostname = sbtConfig.getString("hostname"),
+ actorAkkaPort = sbtConfig.getInt("akka-port")
+ )
logger.info(" runTimeout: {}", runTimeout)
logger.info(" sbtReloadTimeout: {}", sbtReloadTimeout)
@@ -87,4 +85,5 @@ object SbtMain {
()
}
+
}
diff --git a/sbt-runner/src/main/scala/com.olegych.scastie.sbt/SbtProcess.scala b/sbt-runner/src/main/scala/com.olegych.scastie.sbt/SbtProcess.scala
index 95774f696..a334cfa11 100644
--- a/sbt-runner/src/main/scala/com.olegych.scastie.sbt/SbtProcess.scala
+++ b/sbt-runner/src/main/scala/com.olegych.scastie.sbt/SbtProcess.scala
@@ -2,43 +2,44 @@ package com.olegych.scastie.sbt
import java.nio.file._
import java.time.Instant
+import scala.concurrent.duration._
+import scala.util.Random
import akka.actor.{ActorRef, Cancellable, FSM, Stash}
import akka.pattern.ask
import akka.util.Timeout
import com.olegych.scastie.api._
import com.olegych.scastie.instrumentation.InstrumentedInputs
-import com.olegych.scastie.util.ScastieFileUtil.{slurp, write}
import com.olegych.scastie.util._
-
-import scala.concurrent.duration._
-import scala.util.Random
+import com.olegych.scastie.util.ScastieFileUtil.{slurp, write}
object SbtProcess {
sealed trait SbtState
case object Initializing extends SbtState
- case object Ready extends SbtState
- case object Reloading extends SbtState
- case object Running extends SbtState
+ case object Ready extends SbtState
+ case object Reloading extends SbtState
+ case object Running extends SbtState
sealed trait Data
case class SbtData(currentInputs: Inputs) extends Data
+
case class SbtRun(
- snippetId: SnippetId,
- inputs: Inputs,
- isForcedProgramMode: Boolean,
- progressActor: ActorRef,
- snippetActor: ActorRef,
- timeoutEvent: Option[Cancellable]
+ snippetId: SnippetId,
+ inputs: Inputs,
+ isForcedProgramMode: Boolean,
+ progressActor: ActorRef,
+ snippetActor: ActorRef,
+ timeoutEvent: Option[Cancellable]
) extends Data
+
case class SbtStateTimeout(duration: FiniteDuration, state: SbtState) {
+
def message: String = {
- val stateMsg =
- state match {
- case Reloading => "updating build configuration"
- case Running => "running code"
- case _ => sys.error(s"unexpected timeout in state $state")
- }
+ val stateMsg = state match {
+ case Reloading => "updating build configuration"
+ case Running => "running code"
+ case _ => sys.error(s"unexpected timeout in state $state")
+ }
s"timed out after $duration when $stateMsg"
}
@@ -58,19 +59,22 @@ object SbtProcess {
)
)
}
+
}
+
}
-class SbtProcess(runTimeout: FiniteDuration,
- reloadTimeout: FiniteDuration,
- isProduction: Boolean,
- javaOptions: Seq[String],
- customSbtDir: Option[Path] = None)
- extends FSM[SbtProcess.SbtState, SbtProcess.Data]
- with Stash {
+class SbtProcess(
+ runTimeout: FiniteDuration,
+ reloadTimeout: FiniteDuration,
+ isProduction: Boolean,
+ javaOptions: Seq[String],
+ customSbtDir: Option[Path] = None
+) extends FSM[SbtProcess.SbtState, SbtProcess.Data]
+ with Stash {
+ import context.dispatcher
import ProcessActor._
import SbtProcess._
- import context.dispatcher
private var progressId = 0L
@@ -80,15 +84,13 @@ class SbtProcess(runTimeout: FiniteDuration,
run.progressActor ! p
implicit val tm = Timeout(10.seconds)
(run.snippetActor ? p)
- .recover {
- case e =>
- log.error(e, s"error while saving progress $p")
+ .recover { case e =>
+ log.error(e, s"error while saving progress $p")
}
}
- private val sbtDir: Path =
- customSbtDir.getOrElse(Files.createTempDirectory("scastie"))
- private val buildFile = sbtDir.resolve("build.sbt")
+ private val sbtDir: Path = customSbtDir.getOrElse(Files.createTempDirectory("scastie"))
+ private val buildFile = sbtDir.resolve("build.sbt")
private val promptUniqueId = Random.alphanumeric.take(10).mkString
private val projectDir = sbtDir.resolve("project")
@@ -96,7 +98,7 @@ class SbtProcess(runTimeout: FiniteDuration,
// log.info(s"sbtVersion: $sbtVersion")
write(projectDir.resolve("build.properties"), s"sbt.version = ${com.olegych.scastie.buildinfo.BuildInfo.sbtVersion}")
private val pluginFile = projectDir.resolve("plugins.sbt")
- private val codeFile = sbtDir.resolve("src/main/scala/main.scala")
+ private val codeFile = sbtDir.resolve("src/main/scala/main.scala")
Files.createDirectories(codeFile.getParent)
private def scalaJsContent(): Option[String] = {
@@ -122,21 +124,19 @@ class SbtProcess(runTimeout: FiniteDuration,
)
private lazy val process = {
- val sbtOpts =
- (javaOptions ++ Seq(
- "-Djline.terminal=jline.UnsupportedTerminal",
- "-Dsbt.log.noformat=true",
- "-Dsbt.banner=false",
- )).mkString(" ")
-
- val props =
- ProcessActor.props(
- command = List("sbt"),
- workingDir = sbtDir,
- environment = Map(
- "SBT_OPTS" -> sbtOpts
- )
+ val sbtOpts = (javaOptions ++ Seq(
+ "-Djline.terminal=jline.UnsupportedTerminal",
+ "-Dsbt.log.noformat=true",
+ "-Dsbt.banner=false"
+ )).mkString(" ")
+
+ val props = ProcessActor.props(
+ command = List("sbt"),
+ workingDir = sbtDir,
+ environment = Map(
+ "SBT_OPTS" -> sbtOpts
)
+ )
context.actorOf(props, name = s"sbt-process-$promptUniqueId")
}
@@ -157,61 +157,59 @@ class SbtProcess(runTimeout: FiniteDuration,
case _ -> Ready =>
println("-- Ready --")
unstashAll()
- case _ -> Initializing =>
- println("-- Initializing --")
- case _ -> Reloading =>
- println("-- Reloading --")
- case _ -> Running =>
- println("-- Running --")
+ case _ -> Initializing => println("-- Initializing --")
+ case _ -> Reloading => println("-- Reloading --")
+ case _ -> Running => println("-- Running --")
}
- when(Initializing) {
- case Event(out: ProcessOutput, _) =>
- if (isPrompt(out.line)) {
- goto(Ready)
- } else {
- stay()
- }
+ when(Initializing) { case Event(out: ProcessOutput, _) =>
+ if (isPrompt(out.line)) {
+ goto(Ready)
+ } else {
+ stay()
+ }
}
- when(Ready) {
- case Event(task @ SbtTask(snippetId, taskInputs, ip, login, progressActor), SbtData(stateInputs)) =>
- println(s"Running: (login: $login, ip: $ip) \n ${taskInputs.code.take(30)}")
-
- val _sbtRun = SbtRun(
- snippetId = snippetId,
- inputs = taskInputs,
- isForcedProgramMode = false,
- progressActor = progressActor,
- snippetActor = sender(),
- timeoutEvent = None
- )
- sendProgress(_sbtRun, SnippetProgress.default.copy(isDone = false, ts = Some(Instant.now.toEpochMilli), snippetId = Some(snippetId)))
+ when(Ready) { case Event(task @ SbtTask(snippetId, taskInputs, ip, login, progressActor), SbtData(stateInputs)) =>
+ println(s"Running: (login: $login, ip: $ip) \n ${taskInputs.code.take(30)}")
+
+ val _sbtRun = SbtRun(
+ snippetId = snippetId,
+ inputs = taskInputs,
+ isForcedProgramMode = false,
+ progressActor = progressActor,
+ snippetActor = sender(),
+ timeoutEvent = None
+ )
+ sendProgress(
+ _sbtRun,
+ SnippetProgress.default.copy(isDone = false, ts = Some(Instant.now.toEpochMilli), snippetId = Some(snippetId))
+ )
+
+ InstrumentedInputs(taskInputs) match {
+ case Right(instrumented) =>
+ val sbtRun = _sbtRun.copy(inputs = instrumented.inputs, isForcedProgramMode = instrumented.isForcedProgramMode)
+ val isReloading = stateInputs.needsReload(sbtRun.inputs)
+ setInputs(sbtRun.inputs)
+
+ instrumented.optionalParsingError.foreach { error =>
+ sendProgress(sbtRun, error.toProgress(snippetId).copy(isDone = false))
+ }
- InstrumentedInputs(taskInputs) match {
- case Right(instrumented) =>
- val sbtRun = _sbtRun.copy(inputs = instrumented.inputs, isForcedProgramMode = instrumented.isForcedProgramMode)
- val isReloading = stateInputs.needsReload(sbtRun.inputs)
- setInputs(sbtRun.inputs)
-
- instrumented.optionalParsingError.foreach { error =>
- sendProgress(sbtRun, error.toProgress(snippetId).copy(isDone = false))
- }
-
- if (isReloading) {
- process ! Input("reload;compile/compileInputs")
- gotoWithTimeout(sbtRun, Reloading, reloadTimeout)
- } else {
- gotoRunning(sbtRun)
- }
-
- case Left(report) =>
- log.info(s"Instrumentation error: ${report.message}")
- val sbtRun = _sbtRun
- setInputs(sbtRun.inputs)
- sendProgress(sbtRun, report.toProgress(snippetId))
- goto(Ready)
- }
+ if (isReloading) {
+ process ! Input("reload;compile/compileInputs")
+ gotoWithTimeout(sbtRun, Reloading, reloadTimeout)
+ } else {
+ gotoRunning(sbtRun)
+ }
+
+ case Left(report) =>
+ log.info(s"Instrumentation error: ${report.message}")
+ val sbtRun = _sbtRun
+ setInputs(sbtRun.inputs)
+ sendProgress(sbtRun, report.toProgress(snippetId))
+ goto(Ready)
+ }
}
val extractor = new OutputExtractor(
@@ -221,45 +219,42 @@ class SbtProcess(runTimeout: FiniteDuration,
promptUniqueId
)
- when(Reloading) {
- case Event(output: ProcessOutput, sbtRun: SbtRun) =>
- val progress = extractor.extractProgress(output, sbtRun, isReloading = true)
- sendProgress(sbtRun, progress)
+ when(Reloading) { case Event(output: ProcessOutput, sbtRun: SbtRun) =>
+ val progress = extractor.extractProgress(output, sbtRun, isReloading = true)
+ sendProgress(sbtRun, progress)
- if (progress.isSbtError) {
- throw new Exception("sbt error: " + output.line)
- }
+ if (progress.isSbtError) {
+ throw new Exception("sbt error: " + output.line)
+ }
- if (isPrompt(output.line)) {
- gotoRunning(sbtRun)
- } else {
- stay()
- }
+ if (isPrompt(output.line)) {
+ gotoRunning(sbtRun)
+ } else {
+ stay()
+ }
}
- when(Running) {
- case Event(output: ProcessOutput, sbtRun: SbtRun) =>
- val progress = extractor.extractProgress(output, sbtRun, isReloading = false)
- sendProgress(sbtRun, progress)
+ when(Running) { case Event(output: ProcessOutput, sbtRun: SbtRun) =>
+ val progress = extractor.extractProgress(output, sbtRun, isReloading = false)
+ sendProgress(sbtRun, progress)
- if (progress.isDone) {
- sbtRun.timeoutEvent.foreach(_.cancel())
- goto(Ready).using(SbtData(sbtRun.inputs))
- } else {
- stay()
- }
+ if (progress.isDone) {
+ sbtRun.timeoutEvent.foreach(_.cancel())
+ goto(Ready).using(SbtData(sbtRun.inputs))
+ } else {
+ stay()
+ }
}
private def gotoWithTimeout(sbtRun: SbtRun, nextState: SbtState, duration: FiniteDuration): this.State = {
sbtRun.timeoutEvent.foreach(_.cancel())
- val timeout =
- context.system.scheduler.scheduleOnce(
- duration,
- self,
- SbtStateTimeout(duration, nextState)
- )
+ val timeout = context.system.scheduler.scheduleOnce(
+ duration,
+ self,
+ SbtStateTimeout(duration, nextState)
+ )
goto(nextState).using(sbtRun.copy(timeoutEvent = Some(timeout)))
}
@@ -276,8 +271,7 @@ class SbtProcess(runTimeout: FiniteDuration,
// Sbt files setup
private def setInputs(inputs: Inputs): Unit = {
- val prompt =
- s"""shellPrompt := {_ => println(""); "$promptUniqueId" + "\\n "}"""
+ val prompt = s"""shellPrompt := {_ => println(""); "$promptUniqueId" + "\\n "}"""
writeFile(pluginFile, inputs.sbtPluginsConfig + "\n")
writeFile(buildFile, prompt + "\n" + inputs.sbtConfig)
diff --git a/sbt-runner/src/test/scala/com.olegych.scastie.sbt/FormatActorTest.scala b/sbt-runner/src/test/scala/com.olegych.scastie.sbt/FormatActorTest.scala
index 78e1e86a4..5b687d572 100644
--- a/sbt-runner/src/test/scala/com.olegych.scastie.sbt/FormatActorTest.scala
+++ b/sbt-runner/src/test/scala/com.olegych.scastie.sbt/FormatActorTest.scala
@@ -2,8 +2,8 @@ package com.olegych.scastie.sbt
import com.olegych.scastie.api.ScalaTarget
import com.olegych.scastie.sbt.FormatActor
-import org.scalatest.Assertions._
import org.scalatest.funsuite.AnyFunSuite
+import org.scalatest.Assertions._
class FormatActorTest extends AnyFunSuite {
test("format should accept scala 2 code") {
@@ -19,7 +19,7 @@ class FormatActorTest extends AnyFunSuite {
}
test("format should accept scala 2 worksheets") {
- val code = "val x:Int=41+1"
+ val code = "val x:Int=41+1"
val output = "val x: Int = 41 + 1\n"
assert(ScalaTarget.Jvm.default.hasWorksheetMode)
@@ -39,7 +39,7 @@ class FormatActorTest extends AnyFunSuite {
}
test("format should accept scala 3 worksheets") {
- val code = "val x:Int=41+1"
+ val code = "val x:Int=41+1"
val output = "val x: Int = 41 + 1\n"
assert(ScalaTarget.Scala3.default.hasWorksheetMode)
diff --git a/sbt-runner/src/test/scala/com.olegych.scastie.sbt/SbtActorTest.scala b/sbt-runner/src/test/scala/com.olegych.scastie.sbt/SbtActorTest.scala
index 422ae6bae..efd9b5625 100644
--- a/sbt-runner/src/test/scala/com.olegych.scastie.sbt/SbtActorTest.scala
+++ b/sbt-runner/src/test/scala/com.olegych.scastie.sbt/SbtActorTest.scala
@@ -1,16 +1,20 @@
package com.olegych.scastie.sbt
+import scala.concurrent.duration._
+
import akka.actor.{ActorRef, ActorSystem, Props}
-import akka.testkit.TestActor.AutoPilot
import akka.testkit.{ImplicitSender, TestKit, TestProbe}
+import akka.testkit.TestActor.AutoPilot
import com.olegych.scastie.api._
import com.olegych.scastie.util.SbtTask
-import org.scalatest.BeforeAndAfterAll
import org.scalatest.funsuite.AnyFunSuiteLike
+import org.scalatest.BeforeAndAfterAll
-import scala.concurrent.duration._
-
-class SbtActorTest() extends TestKit(ActorSystem("SbtActorTest")) with ImplicitSender with AnyFunSuiteLike with BeforeAndAfterAll {
+class SbtActorTest()
+ extends TestKit(ActorSystem("SbtActorTest"))
+ with ImplicitSender
+ with AnyFunSuiteLike
+ with BeforeAndAfterAll {
setAutoPilot(new AutoPilot {
def run(sender: ActorRef, msg: Any): AutoPilot = {
sender ! s"reply to $msg"
@@ -76,7 +80,7 @@ class SbtActorTest() extends TestKit(ActorSystem("SbtActorTest")) with ImplicitS
val message = "Hello"
runCode(
s"""object Main { def main(args: Array[String]): Unit = println("$message") }""",
- allowFailure = true,
+ allowFailure = true
) { progress =>
if (progress.isDone) progress.isForcedProgramMode else false
}
@@ -115,46 +119,47 @@ class SbtActorTest() extends TestKit(ActorSystem("SbtActorTest")) with ImplicitS
}
test("Scala.js support") {
- val scalaJs =
- Inputs.default.copy(code = "1 + 1", target = ScalaTarget.Js.default)
+ val scalaJs = Inputs.default.copy(code = "1 + 1", target = ScalaTarget.Js.default)
run(scalaJs)(_.isDone)
}
test("Scala.js 3 support") {
- val scalaJs =
- Inputs.default.copy(code = "1 + 1",
- target = ScalaTarget.Js.default.copy(scalaVersion = com.olegych.scastie.buildinfo.BuildInfo.latestLTS))
+ val scalaJs = Inputs.default.copy(
+ code = "1 + 1",
+ target = ScalaTarget.Js.default.copy(scalaVersion = com.olegych.scastie.buildinfo.BuildInfo.latestLTS)
+ )
run(scalaJs)(_.isDone)
}
test("Scala 2.10 support") {
- val scala =
- Inputs.default.copy(code = "println(1 + 1)", target = ScalaTarget.Jvm(com.olegych.scastie.buildinfo.BuildInfo.latest210))
+ val scala = Inputs.default
+ .copy(code = "println(1 + 1)", target = ScalaTarget.Jvm(com.olegych.scastie.buildinfo.BuildInfo.latest210))
run(scala)(assertUserOutput("2"))
}
test("Scala 2.11 support") {
- val scala =
- Inputs.default.copy(code = "println(1 + 1)", target = ScalaTarget.Jvm(com.olegych.scastie.buildinfo.BuildInfo.latest211))
+ val scala = Inputs.default
+ .copy(code = "println(1 + 1)", target = ScalaTarget.Jvm(com.olegych.scastie.buildinfo.BuildInfo.latest211))
run(scala)(assertUserOutput("2"))
}
test("Scala 2.12 support") {
- val scala =
- Inputs.default.copy(code = "println(1 + 1)", target = ScalaTarget.Jvm(com.olegych.scastie.buildinfo.BuildInfo.latest212))
+ val scala = Inputs.default
+ .copy(code = "println(1 + 1)", target = ScalaTarget.Jvm(com.olegych.scastie.buildinfo.BuildInfo.latest212))
run(scala)(assertUserOutput("2"))
}
test("avoid https://github.com/scala/bug/issues/8119") {
- val scala =
- Inputs.default.copy(code = "val n = 0; val m = List(1).par.foreach(_ => n); println(1)",
- target = ScalaTarget.Jvm(com.olegych.scastie.buildinfo.BuildInfo.latest212))
+ val scala = Inputs.default.copy(
+ code = "val n = 0; val m = List(1).par.foreach(_ => n); println(1)",
+ target = ScalaTarget.Jvm(com.olegych.scastie.buildinfo.BuildInfo.latest212)
+ )
run(scala)(assertUserOutput("1"))
}
test("Scala 2.13 support") {
- val scala =
- Inputs.default.copy(code = "println(1 + 1)", target = ScalaTarget.Jvm(com.olegych.scastie.buildinfo.BuildInfo.latest213))
+ val scala = Inputs.default
+ .copy(code = "println(1 + 1)", target = ScalaTarget.Jvm(com.olegych.scastie.buildinfo.BuildInfo.latest213))
run(scala)(assertUserOutput("2"))
}
@@ -165,10 +170,11 @@ class SbtActorTest() extends TestKit(ActorSystem("SbtActorTest")) with ImplicitS
}
test("no warnings on 2.12") {
- val scala =
- Inputs.default.copy(code = "println(1 + 1)",
- sbtConfigExtra = """scalacOptions ++= List("-Xlint", "-Xfatal-warnings")""",
- target = ScalaTarget.Jvm("2.12.10"))
+ val scala = Inputs.default.copy(
+ code = "println(1 + 1)",
+ sbtConfigExtra = """scalacOptions ++= List("-Xlint", "-Xfatal-warnings")""",
+ target = ScalaTarget.Jvm("2.12.10")
+ )
run(scala)(assertUserOutput("2"))
}
@@ -191,7 +197,7 @@ class SbtActorTest() extends TestKit(ActorSystem("SbtActorTest")) with ImplicitS
val message = "Hello, Scala 3 worksheet!"
val dotty = Inputs.default.copy(
code = s"""println("$message")""",
- target = ScalaTarget.Scala3.default,
+ target = ScalaTarget.Scala3.default
)
run(dotty)(assertUserOutput("Hello, Scala 3 worksheet!"))
}
@@ -200,7 +206,7 @@ class SbtActorTest() extends TestKit(ActorSystem("SbtActorTest")) with ImplicitS
val message = "Hello, Scala 3.0 worksheet!"
val dotty = Inputs.default.copy(
code = s"""println("$message")""",
- target = ScalaTarget.Scala3("3.0.0"),
+ target = ScalaTarget.Scala3("3.0.0")
)
run(dotty)(assertUserOutput("Hello, Scala 3.0 worksheet!"))
}
@@ -220,7 +226,7 @@ class SbtActorTest() extends TestKit(ActorSystem("SbtActorTest")) with ImplicitS
val dotty = Inputs.default.copy(
code = s"""|println("Hello world!")
|// test comment""".stripMargin,
- target = ScalaTarget.Jvm.default,
+ target = ScalaTarget.Jvm.default
)
run(dotty)(assertUserOutput("Hello world!"))
}
@@ -229,13 +235,15 @@ class SbtActorTest() extends TestKit(ActorSystem("SbtActorTest")) with ImplicitS
val dotty = Inputs.default.copy(
code = s"""|println("Hello world!")
|// test comment""".stripMargin,
- target = ScalaTarget.Scala3.default,
+ target = ScalaTarget.Scala3.default
)
run(dotty)(assertUserOutput("Hello world!"))
}
test("hide Playground from types") {
- runCode("case class A(i:Int) extends AnyVal; A(1)")(_.instrumentations.headOption.exists(_.render == Value("A(1)", "A")))
+ runCode("case class A(i:Int) extends AnyVal; A(1)")(
+ _.instrumentations.headOption.exists(_.render == Value("A(1)", "A"))
+ )
}
test("#304 null pointer") {
@@ -254,7 +262,7 @@ class SbtActorTest() extends TestKit(ActorSystem("SbtActorTest")) with ImplicitS
}
def assertCompilationInfo(
- infoAssert: Problem => Any
+ infoAssert: Problem => Any
)(progress: SnippetProgress): Boolean = {
val gotCompilationError = progress.compilationInfos.nonEmpty
@@ -287,14 +295,17 @@ class SbtActorTest() extends TestKit(ActorSystem("SbtActorTest")) with ImplicitS
)
private var currentId = 0
+
private def snippetId = {
val t = currentId
currentId += 1
SnippetId(t.toString, None)
}
+
private var firstRun = true
+
private def run(inputs: Inputs, allowFailure: Boolean = false)(fish: SnippetProgress => Boolean): Unit = {
- val ip = "my-ip"
+ val ip = "my-ip"
val progressActor = TestProbe()
sbtActor ! SbtTask(snippetId, inputs, ip, None, progressActor.ref)
@@ -303,26 +314,25 @@ class SbtActorTest() extends TestKit(ActorSystem("SbtActorTest")) with ImplicitS
if (firstRun) timeout + 10.second
else timeout
- progressActor.fishForMessage(totalTimeout + 100.seconds) {
- case progress: SnippetProgress =>
- val fishResult = fish(progress)
- // println(progress -> fishResult)
- if ((progress.isFailure && !allowFailure) || (progress.isDone && !fishResult))
- throw new Exception(s"Fail to meet expectation at ${progress}")
- else fishResult
+ progressActor.fishForMessage(totalTimeout + 100.seconds) { case progress: SnippetProgress =>
+ val fishResult = fish(progress)
+ // println(progress -> fishResult)
+ if ((progress.isFailure && !allowFailure) || (progress.isDone && !fishResult))
+ throw new Exception(s"Fail to meet expectation at ${progress}")
+ else fishResult
}
firstRun = false
}
private def runCode(code: String, target: ScalaTarget = ScalaTarget.Jvm.default, allowFailure: Boolean = false)(
- fish: SnippetProgress => Boolean
+ fish: SnippetProgress => Boolean
): Unit = {
run(Inputs.default.copy(code = code, target = target), allowFailure)(fish)
}
private def assertUserOutput(
- message: String,
- outputType: ProcessOutputType = ProcessOutputType.StdOut
+ message: String,
+ outputType: ProcessOutputType = ProcessOutputType.StdOut
)(progress: SnippetProgress): Boolean = {
val gotHelloMessage = progress.userOutput.exists(out => out.line == message && out.tpe == outputType)
// if (!gotHelloMessage) assert(progress.userOutput.isEmpty)
diff --git a/sbt-scastie/src/main/scala/com.olegych.scastie.sbtscastie/CompilerReporter.scala b/sbt-scastie/src/main/scala/com.olegych.scastie.sbtscastie/CompilerReporter.scala
index 1b7c526b9..65a081249 100644
--- a/sbt-scastie/src/main/scala/com.olegych.scastie.sbtscastie/CompilerReporter.scala
+++ b/sbt-scastie/src/main/scala/com.olegych.scastie.sbtscastie/CompilerReporter.scala
@@ -1,19 +1,17 @@
package com.olegych.scastie.sbtscastie
-import com.olegych.scastie.api
+import java.util.Optional
+import com.olegych.scastie.api
import play.api.libs.json.Json
-
import sbt._
-import Keys._
+import xsbti.{Position, Problem, Reporter, Severity}
import KeyRanks.DTask
-
+import Keys._
import System.{lineSeparator => nl}
-import xsbti.{Reporter, Problem, Position, Severity}
-import java.util.Optional
-
object CompilerReporter {
+
// compilerReporter is marked private in sbt
private lazy val compilerReporter = TaskKey[xsbti.Reporter](
"compilerReporter",
@@ -21,44 +19,43 @@ object CompilerReporter {
DTask
)
- val setting: sbt.Def.Setting[_] =
- Compile / compile / compilerReporter := new xsbti.Reporter {
- private val buffer = collection.mutable.ArrayBuffer.empty[Problem]
- def reset(): Unit = buffer.clear()
- def hasErrors: Boolean = buffer.exists(_.severity == Severity.Error)
- def hasWarnings: Boolean = buffer.exists(_.severity == Severity.Warn)
-
- def printSummary(): Unit = {
- def toApi(p: Problem): api.Problem = {
- def toOption[T](m: Optional[T]): Option[T] = {
- if (!m.isPresent) None
- else Some(m.get)
- }
- val severity =
- p.severity match {
- case xsbti.Severity.Info => api.Info
- case xsbti.Severity.Warn => api.Warning
- case xsbti.Severity.Error => api.Error
- }
- api.Problem(severity, toOption(p.position.line).map(_.toInt), p.message)
+ val setting: sbt.Def.Setting[_] = Compile / compile / compilerReporter := new xsbti.Reporter {
+ private val buffer = collection.mutable.ArrayBuffer.empty[Problem]
+ def reset(): Unit = buffer.clear()
+ def hasErrors: Boolean = buffer.exists(_.severity == Severity.Error)
+ def hasWarnings: Boolean = buffer.exists(_.severity == Severity.Warn)
+
+ def printSummary(): Unit = {
+ def toApi(p: Problem): api.Problem = {
+ def toOption[T](m: Optional[T]): Option[T] = {
+ if (!m.isPresent) None
+ else Some(m.get)
}
- if (problems.nonEmpty) {
- val apiProblems = problems.map(toApi)
- println(Json.stringify(Json.toJson(apiProblems)))
+ val severity = p.severity match {
+ case xsbti.Severity.Info => api.Info
+ case xsbti.Severity.Warn => api.Warning
+ case xsbti.Severity.Error => api.Error
}
+ api.Problem(severity, toOption(p.position.line).map(_.toInt), p.message)
+ }
+ if (problems.nonEmpty) {
+ val apiProblems = problems.map(toApi)
+ println(Json.stringify(Json.toJson(apiProblems)))
}
- def problems: Array[Problem] = buffer.toArray
+ }
+ def problems: Array[Problem] = buffer.toArray
// def log(pos: Position, msg: String, sev: Severity): Unit = {
- def log(problem: Problem): Unit = {
- object MyProblem extends Problem {
- def category: String = "foo"
- def severity: Severity = problem.severity()
- def message: String = problem.message()
- def position: Position = problem.position()
- override def toString = s"$position:$severity: $message"
- }
- buffer.append(MyProblem)
+ def log(problem: Problem): Unit = {
+ object MyProblem extends Problem {
+ def category: String = "foo"
+ def severity: Severity = problem.severity()
+ def message: String = problem.message()
+ def position: Position = problem.position()
+ override def toString = s"$position:$severity: $message"
}
- def comment(pos: xsbti.Position, msg: String): Unit = ()
+ buffer.append(MyProblem)
}
+ def comment(pos: xsbti.Position, msg: String): Unit = ()
+ }
+
}
diff --git a/sbt-scastie/src/main/scala/com.olegych.scastie.sbtscastie/RuntimeErrorLogger.scala b/sbt-scastie/src/main/scala/com.olegych.scastie.sbtscastie/RuntimeErrorLogger.scala
index 46e15dd83..e961ebeb2 100644
--- a/sbt-scastie/src/main/scala/com.olegych.scastie.sbtscastie/RuntimeErrorLogger.scala
+++ b/sbt-scastie/src/main/scala/com.olegych.scastie.sbtscastie/RuntimeErrorLogger.scala
@@ -1,19 +1,20 @@
package sbt.internal.util.com.olegych.scastie.sbtscastie
+import java.io.{OutputStream, PrintWriter}
+import java.nio.channels.ClosedChannelException
+import java.util.concurrent.atomic.AtomicReference
+
import com.olegych.scastie.api._
import org.apache.logging.log4j.core.{Appender => XAppender, LogEvent => XLogEvent}
import org.apache.logging.log4j.message.ObjectMessage
import play.api.libs.json.Json
-import sbt.Keys._
import sbt._
-import sbt.internal.util.ConsoleAppender.Properties
import sbt.internal.util.{ConsoleAppender, Log4JConsoleAppender, ObjectEvent, TraceEvent}
-
-import java.io.{OutputStream, PrintWriter}
-import java.nio.channels.ClosedChannelException
-import java.util.concurrent.atomic.AtomicReference
+import sbt.internal.util.ConsoleAppender.Properties
+import sbt.Keys._
object RuntimeErrorLogger {
+
private val scastieOut = new PrintWriter(new OutputStream {
def out(in: String): Unit = {
println(
@@ -24,37 +25,37 @@ object RuntimeErrorLogger {
)
)
}
- override def write(b: Int): Unit = ()
- override def write(b: Array[Byte]): Unit = out(new String(b))
+ override def write(b: Int): Unit = ()
+ override def write(b: Array[Byte]): Unit = out(new String(b))
override def write(b: Array[Byte], off: Int, len: Int): Unit = out(new String(b, off, len))
- override def close(): Unit = ()
- override def flush(): Unit = ()
+ override def close(): Unit = ()
+ override def flush(): Unit = ()
})
private def findThrowable(event: XLogEvent) = {
- //daaamn
+ // daaamn
Option(event.getThrown).orElse {
for {
- e <- Option(event.getMessage).collect {
- case e: ObjectMessage => e
+ e <- Option(event.getMessage).collect { case e: ObjectMessage =>
+ e
}
- e <- Option(e.getParameter).collect {
- case e: ObjectEvent[_] => e
+ e <- Option(e.getParameter).collect { case e: ObjectEvent[_] =>
+ e
}
- e <- Option(e.message).collect {
- case e: TraceEvent => e
+ e <- Option(e.message).collect { case e: TraceEvent =>
+ e
}
- //since worksheet wraps the code in object we unwrap it to display clearer message
+ // since worksheet wraps the code in object we unwrap it to display clearer message
e <- Option(e.message).collect {
case e: ExceptionInInitializerError if e.getCause != null && e.getCause.getStackTrace.headOption.exists { e =>
e.getClassName == Instrumentation.instrumentedObject + "$" && e.getMethodName == ""
- } =>
- e.getCause
+ } => e.getCause
case e => e
}
} yield e
}
}
+
private def logThrowable(throwable: Throwable): Unit = {
val error = RuntimeErrorWrap(RuntimeError.fromThrowable(throwable))
println(Json.stringify(Json.toJson(error)))
@@ -62,35 +63,39 @@ object RuntimeErrorLogger {
val settings: Seq[sbt.Def.Setting[_]] = Seq(
showSuccess := false,
- useLog4J := true,
- logManager := sbt.internal.LogManager.withLoggers(
- (_, _) =>
- new ConsoleAppender(ConsoleAppender.generateName, Properties.from(ConsoleOut.printWriterOut(scastieOut), true, false), _ => None) {
- override def trace(t: => Throwable, traceLevel: Int): Unit = logThrowable(t)
- private[this] val log4j = new AtomicReference[XAppender](null)
- private[sbt] override lazy val toLog4J = log4j.get match {
- case null =>
- log4j.synchronized {
- log4j.get match {
- case null =>
- val l = new Log4JConsoleAppender(
- name,
- properties,
- suppressedMessage, { event =>
- val level = ConsoleAppender.toLevel(event.getLevel)
- val message = event.getMessage
- findThrowable(event).foreach(logThrowable)
- try appendMessage(level, message)
- catch { case _: ClosedChannelException => }
- }
- )
- log4j.set(l)
- l
- case l => l
- }
+ useLog4J := true,
+ logManager := sbt.internal.LogManager.withLoggers((_, _) =>
+ new ConsoleAppender(
+ ConsoleAppender.generateName,
+ Properties.from(ConsoleOut.printWriterOut(scastieOut), true, false),
+ _ => None
+ ) {
+ override def trace(t: => Throwable, traceLevel: Int): Unit = logThrowable(t)
+ private[this] val log4j = new AtomicReference[XAppender](null)
+ private[sbt] override lazy val toLog4J = log4j.get match {
+ case null => log4j.synchronized {
+ log4j.get match {
+ case null =>
+ val l = new Log4JConsoleAppender(
+ name,
+ properties,
+ suppressedMessage,
+ { event =>
+ val level = ConsoleAppender.toLevel(event.getLevel)
+ val message = event.getMessage
+ findThrowable(event).foreach(logThrowable)
+ try appendMessage(level, message)
+ catch { case _: ClosedChannelException => }
+ }
+ )
+ log4j.set(l)
+ l
+ case l => l
}
- }
+ }
+ }
}
- ),
+ )
)
+
}
diff --git a/sbt-scastie/src/main/scala/com.olegych.scastie.sbtscastie/SbtScastiePlugin.scala b/sbt-scastie/src/main/scala/com.olegych.scastie.sbtscastie/SbtScastiePlugin.scala
index 62221b6dc..b786bcb40 100644
--- a/sbt-scastie/src/main/scala/com.olegych.scastie.sbtscastie/SbtScastiePlugin.scala
+++ b/sbt-scastie/src/main/scala/com.olegych.scastie.sbtscastie/SbtScastiePlugin.scala
@@ -1,32 +1,31 @@
package com.olegych.scastie
package sbtscastie
-import sbt.Keys.*
+import scala.util.{Failure, Success, Try}
+
import sbt.*
import sbt.internal.inc.AnalyzingCompiler
-
-import scala.util.{Success, Try, Failure}
+import sbt.Keys.*
object SbtScastiePlugin extends AutoPlugin {
override def requires = sbt.plugins.JvmPlugin
- override def trigger = allRequirements
+ override def trigger = allRequirements
override lazy val projectSettings: Seq[sbt.Def.Setting[_]] =
(CompilerReporter.setting +: sbt.internal.util.com.olegych.scastie.sbtscastie.RuntimeErrorLogger.settings) ++
Seq(
- //workaround https://github.com/sbt/sbt/issues/5482
+ // workaround https://github.com/sbt/sbt/issues/5482
Global / nio.Keys.onChangedBuildSource := nio.Keys.IgnoreSourceChanges,
- turbo := true,
- useSuperShell := false,
- autoStartServer := false,
+ turbo := true,
+ useSuperShell := false,
+ autoStartServer := false,
compilers := {
val r = compilers.value
- //compile bridge to init everything on reload
+ // compile bridge to init everything on reload
r.scalac() match {
- case c: AnalyzingCompiler =>
- c.provider.fetchCompiledBridge(c.scalaInstance, streams.value.log)
- case _ => ()
+ case c: AnalyzingCompiler => c.provider.fetchCompiledBridge(c.scalaInstance, streams.value.log)
+ case _ => ()
}
r
},
@@ -49,10 +48,13 @@ object SbtScastiePlugin extends AutoPlugin {
resolvers := {
Seq[Resolver](
Resolver
- .url("my-ivy-proxy-releases", url("http://scala-webapps.epfl.ch:8081/artifactory/scastie-ivy/"))(Resolver.ivyStylePatterns)
+ .url("my-ivy-proxy-releases", url("http://scala-webapps.epfl.ch:8081/artifactory/scastie-ivy/"))(
+ Resolver.ivyStylePatterns
+ )
.withAllowInsecureProtocol(true),
- "my-maven-proxy-releases" at "http://scala-webapps.epfl.ch:8081/artifactory/scastie-maven/" withAllowInsecureProtocol (true),
+ "my-maven-proxy-releases" at "http://scala-webapps.epfl.ch:8081/artifactory/scastie-maven/" withAllowInsecureProtocol (true)
) ++ resolvers.value
- },
+ }
)
+
}
diff --git a/sbt-scastie/src/main/scala/sbt/ScastieTrapExit.scala b/sbt-scastie/src/main/scala/sbt/ScastieTrapExit.scala
index bdd61508a..c9f3c1f98 100644
--- a/sbt-scastie/src/main/scala/sbt/ScastieTrapExit.scala
+++ b/sbt-scastie/src/main/scala/sbt/ScastieTrapExit.scala
@@ -7,56 +7,52 @@
package sbt
-import scala.annotation.nowarn
-import scala.reflect.Manifest
-import scala.collection.concurrent.TrieMap
import java.lang.ref.WeakReference
-import Thread.currentThread
+import java.lang.Integer.{toHexString => hex}
import java.security.Permission
import java.util.concurrent.{ConcurrentHashMap => CMap}
-import java.lang.Integer.{toHexString => hex}
import java.util.function.Supplier
+import scala.annotation.nowarn
+import scala.collection.concurrent.TrieMap
+import scala.reflect.Manifest
import sbt.util.InterfaceUtil
import ScastieTrapExit._
+import Thread.currentThread
/**
- * Provides an approximation to isolated execution within a single JVM.
- * System.exit calls are trapped to prevent the JVM from terminating. This is useful for executing
- * user code that may call System.exit, but actually exiting is undesirable.
- *
- * Exit is simulated by disposing all top-level windows and interrupting user-started threads.
- * Threads are not stopped and shutdown hooks are not called. It is
- * therefore inappropriate to use this with code that requires shutdown hooks, creates threads that
- * do not terminate, or if concurrent AWT applications are run.
- * This category of code should only be called by forking a new JVM.
- */
+ * Provides an approximation to isolated execution within a single JVM. System.exit calls are trapped to prevent the
+ * JVM from terminating. This is useful for executing user code that may call System.exit, but actually exiting is
+ * undesirable.
+ *
+ * Exit is simulated by disposing all top-level windows and interrupting user-started threads. Threads are not stopped
+ * and shutdown hooks are not called. It is therefore inappropriate to use this with code that requires shutdown hooks,
+ * creates threads that do not terminate, or if concurrent AWT applications are run. This category of code should only
+ * be called by forking a new JVM.
+ */
@nowarn
object ScastieTrapExit {
/**
- * Run `execute` in a managed context, using `log` for debugging messages.
- * `installManager` must be called before calling this method.
- */
- def apply(execute: => Unit, log: Logger): Int =
- System.getSecurityManager match {
- case m: ScastieTrapExit => m.runManaged(InterfaceUtil.toSupplier(execute), log)
- case _ => runUnmanaged(execute, log)
- }
+ * Run `execute` in a managed context, using `log` for debugging messages. `installManager` must be called before
+ * calling this method.
+ */
+ def apply(execute: => Unit, log: Logger): Int = System.getSecurityManager match {
+ case m: ScastieTrapExit => m.runManaged(InterfaceUtil.toSupplier(execute), log)
+ case _ => runUnmanaged(execute, log)
+ }
/**
- * Installs the SecurityManager that implements the isolation and returns the previously installed SecurityManager, which may be null.
- * This method must be called before using `apply`.
- */
- def installManager(): SecurityManager =
- System.getSecurityManager match {
- case m: ScastieTrapExit => m
- case m => System.setSecurityManager(new ScastieTrapExit(m)); m
- }
+ * Installs the SecurityManager that implements the isolation and returns the previously installed SecurityManager,
+ * which may be null. This method must be called before using `apply`.
+ */
+ def installManager(): SecurityManager = System.getSecurityManager match {
+ case m: ScastieTrapExit => m
+ case m => System.setSecurityManager(new ScastieTrapExit(m)); m
+ }
/** Uninstalls the isolation SecurityManager and restores the old security manager. */
- def uninstallManager(previous: SecurityManager): Unit =
- System.setSecurityManager(previous)
+ def uninstallManager(previous: SecurityManager): Unit = System.setSecurityManager(previous)
private[this] def runUnmanaged(execute: => Unit, log: Logger): Int = {
log.warn("Managed execution not possible: security manager not installed.")
@@ -72,11 +68,10 @@ object ScastieTrapExit {
private type ThreadID = String
- /** `true` if the thread `t` is in the TERMINATED state.x*/
+ /** `true` if the thread `t` is in the TERMINATED state.x */
private def isDone(t: Thread): Boolean = t.getState == Thread.State.TERMINATED
- private def computeID(g: ThreadGroup): ThreadID =
- s"g:${hex(System.identityHashCode(g))}:${g.getName}"
+ private def computeID(g: ThreadGroup): ThreadID = s"g:${hex(System.identityHashCode(g))}:${g.getName}"
/** Computes an identifier for a Thread that has a high probability of being unique within a single JVM execution. */
private def computeID(t: Thread): ThreadID =
@@ -85,7 +80,9 @@ object ScastieTrapExit {
// Apple AWT: +[ThreadUtilities getJNIEnvUncached] attempting to attach current thread after JNFObtainEnv() failed
s"${hex(System.identityHashCode(t))}"
- /** Waits for the given `thread` to terminate. However, if the thread state is NEW, this method returns immediately. */
+ /**
+ * Waits for the given `thread` to terminate. However, if the thread state is NEW, this method returns immediately.
+ */
private def waitOnThread(thread: Thread, log: Logger): Unit = {
log.debug("Waiting for thread " + thread.getName + " to terminate.")
thread.join
@@ -99,8 +96,11 @@ object ScastieTrapExit {
thread.interrupt
log.debug("\tInterrupted " + thread.getName)
}
+
// an uncaught exception handler that swallows InterruptedExceptions and otherwise defers to originalHandler
- private final class TrapInterrupt(originalHandler: Thread.UncaughtExceptionHandler) extends Thread.UncaughtExceptionHandler {
+ private final class TrapInterrupt(originalHandler: Thread.UncaughtExceptionHandler)
+ extends Thread.UncaughtExceptionHandler {
+
def uncaughtException(thread: Thread, e: Throwable): Unit = {
withCause[InterruptedException, Unit](e) { interrupted =>
()
@@ -109,47 +109,50 @@ object ScastieTrapExit {
}
thread.setUncaughtExceptionHandler(originalHandler)
}
+
}
/**
- * Recurses into the causes of the given exception looking for a cause of type CauseType. If one is found, `withType` is called with that cause.
- * If not, `notType` is called with the root cause.
- */
+ * Recurses into the causes of the given exception looking for a cause of type CauseType. If one is found, `withType`
+ * is called with that cause. If not, `notType` is called with the root cause.
+ */
private def withCause[CauseType <: Throwable, T](
- e: Throwable
- )(withType: CauseType => T)(notType: Throwable => T)(implicit mf: Manifest[CauseType]): T = {
+ e: Throwable
+ )(withType: CauseType => T)(notType: Throwable => T)(
+ implicit mf: Manifest[CauseType]
+ ): T = {
val clazz = mf.runtimeClass
- if (clazz.isInstance(e))
- withType(e.asInstanceOf[CauseType])
+ if (clazz.isInstance(e)) withType(e.asInstanceOf[CauseType])
else {
val cause = e.getCause
- if (cause == null)
- notType(e)
- else
- withCause(cause)(withType)(notType)(mf)
+ if (cause == null) notType(e)
+ else withCause(cause)(withType)(notType)(mf)
}
}
}
/**
- * Simulates isolation via a SecurityManager.
- * Multiple applications are supported by tracking Thread constructions via `checkAccess`.
- * The Thread that constructed each Thread is used to map a new Thread to an application.
- * This is not reliable on all jvms, so ThreadGroup creations are also tracked via
- * `checkAccess` and traversed on demand to collect threads.
- * This association of Threads with an application allows properly waiting for
- * non-daemon threads to terminate or to interrupt the correct threads when terminating.
- * It also allows disposing AWT windows if the application created any.
- * Only one AWT application is supported at a time, however.
- */
+ * Simulates isolation via a SecurityManager. Multiple applications are supported by tracking Thread constructions via
+ * `checkAccess`. The Thread that constructed each Thread is used to map a new Thread to an application. This is not
+ * reliable on all jvms, so ThreadGroup creations are also tracked via `checkAccess` and traversed on demand to collect
+ * threads. This association of Threads with an application allows properly waiting for non-daemon threads to terminate
+ * or to interrupt the correct threads when terminating. It also allows disposing AWT windows if the application
+ * created any. Only one AWT application is supported at a time, however.
+ */
@nowarn
private final class ScastieTrapExit(delegateManager: SecurityManager) extends SecurityManager {
- /** Tracks the number of running applications in order to short-cut SecurityManager checks when no applications are active.*/
+ /**
+ * Tracks the number of running applications in order to short-cut SecurityManager checks when no applications are
+ * active.
+ */
private[this] val running = new java.util.concurrent.atomic.AtomicInteger
- /** Maps a thread or thread group to its originating application. The thread is represented by a unique identifier to avoid leaks. */
+ /**
+ * Maps a thread or thread group to its originating application. The thread is represented by a unique identifier to
+ * avoid leaks.
+ */
private[this] val threadToApp = new CMap[ThreadID, App]
/** Executes `f` in a managed context. */
@@ -160,9 +163,10 @@ private final class ScastieTrapExit(delegateManager: SecurityManager) extends Se
running.decrementAndGet(); ()
}
}
+
private[this] def runManaged0(f: Supplier[Unit], xlog: xsbti.Logger): Int = {
- val log: Logger = xlog
- val app = new App(f, log)
+ val log: Logger = xlog
+ val app = new App(f, log)
val executionThread = app.mainThread
try {
executionThread.start() // thread actually evaluating `f`
@@ -182,9 +186,9 @@ private final class ScastieTrapExit(delegateManager: SecurityManager) extends Se
}
/**
- * Wait for all non-daemon threads for `app` to exit, for an exception to be thrown in the main thread,
- * or for `System.exit` to be called in a thread started by `app`.
- */
+ * Wait for all non-daemon threads for `app` to exit, for an exception to be thrown in the main thread, or for
+ * `System.exit` to be called in a thread started by `app`.
+ */
private[this] def finish(app: App, log: Logger): Int = {
log.debug("Waiting for threads to exit or System.exit to be called.")
waitForExit(app)
@@ -207,28 +211,25 @@ private final class ScastieTrapExit(delegateManager: SecurityManager) extends Se
}
}
// processThreads takes a snapshot of the threads at a given moment, so if there were only daemons, the application should shut down
- if (!daemonsOnly)
- waitForExit(app)
+ if (!daemonsOnly) waitForExit(app)
}
/** Gives managed applications a unique ID to use in the IDs of the main thread and thread group. */
- private[this] val nextAppID = new java.util.concurrent.atomic.AtomicLong
+ private[this] val nextAppID = new java.util.concurrent.atomic.AtomicLong
private def nextID(): String = nextAppID.getAndIncrement.toHexString
/**
- * Represents an isolated application as simulated by [[ScastieTrapExit]].
- * `execute` is the application code to evaluate.
- * `log` is used for debug logging.
- */
+ * Represents an isolated application as simulated by [[ScastieTrapExit]]. `execute` is the application code to
+ * evaluate. `log` is used for debug logging.
+ */
private final class App(val execute: Supplier[Unit], val log: Logger) extends Runnable {
/**
- * Tracks threads and groups created by this application.
- * To avoid leaks, keys are a unique identifier and values are held via WeakReference.
- * A TrieMap supports the necessary concurrent updates and snapshots.
- */
+ * Tracks threads and groups created by this application. To avoid leaks, keys are a unique identifier and values
+ * are held via WeakReference. A TrieMap supports the necessary concurrent updates and snapshots.
+ */
private[this] val threads = new TrieMap[ThreadID, WeakReference[Thread]]
- private[this] val groups = new TrieMap[ThreadID, WeakReference[ThreadGroup]]
+ private[this] val groups = new TrieMap[ThreadID, WeakReference[ThreadGroup]]
/** Tracks whether AWT has ever been used in this jvm execution. */
@volatile
@@ -239,15 +240,15 @@ private final class ScastieTrapExit(delegateManager: SecurityManager) extends Se
/** The ThreadGroup to use to try to track created threads. */
val mainGroup: ThreadGroup = new ThreadGroup("run-main-group-" + id) {
- private[this] val handler = new LoggingExceptionHandler(log, None)
- override def uncaughtException(t: Thread, e: Throwable): Unit =
- handler.uncaughtException(t, e)
+ private[this] val handler = new LoggingExceptionHandler(log, None)
+ override def uncaughtException(t: Thread, e: Throwable): Unit = handler.uncaughtException(t, e)
}
+
val mainThread = new Thread(mainGroup, this, "run-main-" + id)
/** Saves the ids of the creating thread and thread group to avoid tracking them as coming from this application. */
val creatorThreadID = computeID(currentThread)
- val creatorGroup = currentThread.getThreadGroup
+ val creatorGroup = currentThread.getThreadGroup
register(mainThread)
register(mainGroup)
@@ -258,26 +259,25 @@ private final class ScastieTrapExit(delegateManager: SecurityManager) extends Se
try execute.get()
catch {
case x: Throwable =>
- exitCode.set(1) //exceptions in the main thread cause the exit code to be 1
+ exitCode.set(1) // exceptions in the main thread cause the exit code to be 1
throw x
}
}
- /** Records a new group both in the global [[ScastieTrapExit]] manager and for this [[App]].*/
- def register(g: ThreadGroup): Unit =
- if (g != null && g != creatorGroup && !isSystemGroup(g)) {
- val groupID = computeID(g)
- val old = groups.putIfAbsent(groupID, new WeakReference(g))
- if (old.isEmpty) { // wasn't registered
- threadToApp.put(groupID, this)
- ()
- }
+ /** Records a new group both in the global [[ScastieTrapExit]] manager and for this [[App]]. */
+ def register(g: ThreadGroup): Unit = if (g != null && g != creatorGroup && !isSystemGroup(g)) {
+ val groupID = computeID(g)
+ val old = groups.putIfAbsent(groupID, new WeakReference(g))
+ if (old.isEmpty) { // wasn't registered
+ threadToApp.put(groupID, this)
+ ()
}
+ }
/**
- * Records a new thread both in the global [[ScastieTrapExit]] manager and for this [[App]].
- * Its uncaught exception handler is configured to log exceptions through `log`.
- */
+ * Records a new thread both in the global [[ScastieTrapExit]] manager and for this [[App]]. Its uncaught exception
+ * handler is configured to log exceptions through `log`.
+ */
def register(t: Thread): Unit = {
val threadID = computeID(t)
if (!isDone(t) && threadID != creatorThreadID) {
@@ -285,8 +285,7 @@ private final class ScastieTrapExit(delegateManager: SecurityManager) extends Se
if (old.isEmpty) { // wasn't registered
threadToApp.put(threadID, this)
setExceptionHandler(t)
- if (!awtUsed && isEventQueue(t))
- awtUsed = true
+ if (!awtUsed && isEventQueue(t)) awtUsed = true
}
}
}
@@ -314,11 +313,11 @@ private final class ScastieTrapExit(delegateManager: SecurityManager) extends Se
cleanup(threads)
cleanup(groups)
}
+
private[this] def cleanup(resources: TrieMap[ThreadID, _]): Unit = {
val snap = resources.readOnlySnapshot
resources.clear()
- for ((id, _) <- snap)
- unregister(id)
+ for ((id, _) <- snap) unregister(id)
}
// only want to operate on unterminated threads
@@ -332,19 +331,16 @@ private final class ScastieTrapExit(delegateManager: SecurityManager) extends Se
val snap = threads.readOnlySnapshot
for ((id, tref) <- snap) {
val t = tref.get
- if ((t eq null) || isDone(t))
- unregister(id)
+ if ((t eq null) || isDone(t)) unregister(id)
else {
f(t)
- if (isDone(t))
- unregister(id)
+ if (isDone(t)) unregister(id)
}
}
}
// registers Threads from the tracked ThreadGroups
- private[this] def addUntrackedThreads(): Unit =
- groupThreadsSnapshot foreach register
+ private[this] def addUntrackedThreads(): Unit = groupThreadsSnapshot foreach register
private[this] def groupThreadsSnapshot: Seq[Thread] = {
val snap = groups.readOnlySnapshot.values.map(_.get).filter(_ != null)
@@ -355,8 +351,8 @@ private final class ScastieTrapExit(delegateManager: SecurityManager) extends Se
// the thread groups are accumulated in `accum` and then the threads in each are collected all at
// once while they are all locked. This is the closest thing to a snapshot that can be accomplished.
private[this] def threadsInGroups(
- toProcess: List[ThreadGroup],
- accum: List[ThreadGroup]
+ toProcess: List[ThreadGroup],
+ accum: List[ThreadGroup]
): List[Thread] = toProcess match {
case group :: tail =>
// ThreadGroup implementation synchronizes on its methods, so by synchronizing here, we can workaround its quirks somewhat
@@ -369,35 +365,34 @@ private final class ScastieTrapExit(delegateManager: SecurityManager) extends Se
// gets the immediate child ThreadGroups of `group`
private[this] def threadGroups(group: ThreadGroup): List[ThreadGroup] = {
- val upperBound = group.activeGroupCount
- val groups = new Array[ThreadGroup](upperBound)
+ val upperBound = group.activeGroupCount
+ val groups = new Array[ThreadGroup](upperBound)
val childrenCount = group.enumerate(groups, false)
groups.take(childrenCount).toList
}
// gets the immediate child Threads of `group`
private[this] def threads(group: ThreadGroup): List[Thread] = {
- val upperBound = group.activeCount
- val threads = new Array[Thread](upperBound)
+ val upperBound = group.activeCount
+ val threads = new Array[Thread](upperBound)
val childrenCount = group.enumerate(threads, false)
threads.take(childrenCount).toList
}
+
}
private[this] def stopAllThreads(app: App): Unit = {
// only try to dispose frames if we think the App used AWT
// otherwise, we initialize AWT as a side effect of asking for the frames
// also, we only assume one AWT application at a time
- if (app.awtUsed)
- disposeAllFrames(app.log)
+ if (app.awtUsed) disposeAllFrames(app.log)
interruptAllThreads(app)
}
- private[this] def interruptAllThreads(app: App): Unit =
- app processThreads { t =>
- if (!isSystemThread(t)) safeInterrupt(t, app.log)
- else app.log.debug(s"Not interrupting system thread $t")
- }
+ private[this] def interruptAllThreads(app: App): Unit = app processThreads { t =>
+ if (!isSystemThread(t)) safeInterrupt(t, app.log)
+ else app.log.debug(s"Not interrupting system thread $t")
+ }
/** Gets the managed application associated with Thread `t` */
private[this] def getApp(t: Thread): Option[App] =
@@ -408,9 +403,9 @@ private final class ScastieTrapExit(delegateManager: SecurityManager) extends Se
Option(group).flatMap(g => Option(threadToApp.get(computeID(g))))
/**
- * Handles a valid call to `System.exit` by setting the exit code and
- * interrupting remaining threads for the application associated with `t`, if one exists.
- */
+ * Handles a valid call to `System.exit` by setting the exit code and interrupting remaining threads for the
+ * application associated with `t`, if one exists.
+ */
private[this] def exitApp(t: Thread, status: Int): Unit = getApp(t) match {
case None => System.err.println(s"Could not exit($status): no application associated with $t")
case Some(a) =>
@@ -418,9 +413,9 @@ private final class ScastieTrapExit(delegateManager: SecurityManager) extends Se
stopAllThreads(a)
}
- /** SecurityManager hook to trap calls to `System.exit` to avoid shutting down the whole JVM.*/
+ /** SecurityManager hook to trap calls to `System.exit` to avoid shutting down the whole JVM. */
override def checkExit(status: Int): Unit = if (active) {
- val t = currentThread
+ val t = currentThread
val stack = t.getStackTrace
if (stack == null || stack.exists(isRealExit)) {
exitApp(t, status)
@@ -428,33 +423,32 @@ private final class ScastieTrapExit(delegateManager: SecurityManager) extends Se
}
}
- /** This ensures that only actual calls to exit are trapped and not just calls to check if exit is allowed.*/
+ /** This ensures that only actual calls to exit are trapped and not just calls to check if exit is allowed. */
private def isRealExit(element: StackTraceElement): Boolean =
element.getClassName == "java.lang.Runtime" && element.getMethodName == "exit"
// These are overridden to do nothing because there is a substantial filesystem performance penalty
// when there is a SecurityManager defined. The default implementations of these construct a
// FilePermission, and its initialization involves canonicalization, which is expensive.
- override def checkRead(file: String): Unit = ()
+ override def checkRead(file: String): Unit = ()
override def checkRead(file: String, context: AnyRef): Unit = ()
- override def checkWrite(file: String): Unit = ()
- override def checkDelete(file: String): Unit = ()
- override def checkExec(cmd: String): Unit = ()
+ override def checkWrite(file: String): Unit = ()
+ override def checkDelete(file: String): Unit = ()
+ override def checkExec(cmd: String): Unit = ()
override def checkPermission(perm: Permission): Unit = {
- if (delegateManager ne null)
- delegateManager.checkPermission(perm)
+ if (delegateManager ne null) delegateManager.checkPermission(perm)
}
+
override def checkPermission(perm: Permission, context: AnyRef): Unit = {
- if (delegateManager ne null)
- delegateManager.checkPermission(perm, context)
+ if (delegateManager ne null) delegateManager.checkPermission(perm, context)
}
/**
- * SecurityManager hook that is abused to record every created Thread and associate it with a managed application.
- * This is not reliably called on different jvm implementations. On openjdk and similar jvms, the Thread constructor
- * calls setPriority, which triggers this SecurityManager check. For Java 6 on OSX, this is not called, however.
- */
+ * SecurityManager hook that is abused to record every created Thread and associate it with a managed application.
+ * This is not reliably called on different jvm implementations. On openjdk and similar jvms, the Thread constructor
+ * calls setPriority, which triggers this SecurityManager check. For Java 6 on OSX, this is not called, however.
+ */
override def checkAccess(t: Thread): Unit = {
if (active) {
val group = t.getThreadGroup
@@ -464,14 +458,13 @@ private final class ScastieTrapExit(delegateManager: SecurityManager) extends Se
app.register(currentThread)
}
}
- if (delegateManager ne null)
- delegateManager.checkAccess(t)
+ if (delegateManager ne null) delegateManager.checkAccess(t)
}
/**
- * This is specified to be called in every Thread's constructor and every time a ThreadGroup is created.
- * This allows us to reliably track every ThreadGroup that is created and map it back to the constructing application.
- */
+ * This is specified to be called in every Thread's constructor and every time a ThreadGroup is created. This allows
+ * us to reliably track every ThreadGroup that is created and map it back to the constructing application.
+ */
override def checkAccess(tg: ThreadGroup): Unit = {
if (active && !isSystemGroup(tg)) {
noteAccess(tg) { app =>
@@ -480,15 +473,13 @@ private final class ScastieTrapExit(delegateManager: SecurityManager) extends Se
}
}
- if (delegateManager ne null)
- delegateManager.checkAccess(tg)
+ if (delegateManager ne null) delegateManager.checkAccess(tg)
}
private[this] def noteAccess(group: ThreadGroup)(f: App => Unit): Unit =
getApp(currentThread) orElse getApp(group) foreach f
- private[this] def isSystemGroup(group: ThreadGroup): Boolean =
- (group != null) && (group.getName == "system")
+ private[this] def isSystemGroup(group: ThreadGroup): Boolean = (group != null) && (group.getName == "system")
/** `true` if there is at least one application currently being managed. */
private[this] def active = running.get > 0
@@ -497,45 +488,46 @@ private final class ScastieTrapExit(delegateManager: SecurityManager) extends Se
val allFrames = java.awt.Frame.getFrames
if (allFrames.nonEmpty) {
log.debug(s"Disposing ${allFrames.length} top-level windows...")
- allFrames.foreach(_.dispose) // dispose all top-level windows, which will cause the AWT-EventQueue-* threads to exit
+ allFrames.foreach(
+ _.dispose
+ ) // dispose all top-level windows, which will cause the AWT-EventQueue-* threads to exit
val waitSeconds = 2
log.debug(s"Waiting $waitSeconds s to let AWT thread exit.")
Thread.sleep(waitSeconds * 1000L) // AWT Thread doesn't exit immediately, so wait to interrupt it
}
}
- /** Returns true if the given thread is in the 'system' thread group or is an AWT thread other than AWT-EventQueue.*/
+ /** Returns true if the given thread is in the 'system' thread group or is an AWT thread other than AWT-EventQueue. */
private def isSystemThread(t: Thread) =
- if (t.getName.startsWith("AWT-"))
- !isEventQueue(t)
- else
- isSystemGroup(t.getThreadGroup)
+ if (t.getName.startsWith("AWT-")) !isEventQueue(t)
+ else isSystemGroup(t.getThreadGroup)
/**
- * An App is identified as using AWT if it gets associated with the event queue thread.
- * The event queue thread is not treated as a system thread.
- */
+ * An App is identified as using AWT if it gets associated with the event queue thread. The event queue thread is not
+ * treated as a system thread.
+ */
private[this] def isEventQueue(t: Thread): Boolean = t.getName.startsWith("AWT-EventQueue")
}
-/** A thread-safe, write-once, optional cell for tracking an application's exit code.*/
+/** A thread-safe, write-once, optional cell for tracking an application's exit code. */
private final class ExitCode {
private var code: Option[Int] = None
- def set(c: Int): Unit = synchronized { code = code orElse Some(c) }
- def value: Option[Int] = synchronized { code }
+ def set(c: Int): Unit = synchronized { code = code orElse Some(c) }
+ def value: Option[Int] = synchronized { code }
}
/**
- * The default uncaught exception handler for managed executions.
- * It logs the thread and the exception.
- */
+ * The default uncaught exception handler for managed executions. It logs the thread and the exception.
+ */
private final class LoggingExceptionHandler(
- log: Logger,
- delegate: Option[Thread.UncaughtExceptionHandler]
+ log: Logger,
+ delegate: Option[Thread.UncaughtExceptionHandler]
) extends Thread.UncaughtExceptionHandler {
+
def uncaughtException(t: Thread, e: Throwable): Unit = {
log.error("(" + t.getName + ") " + e.toString)
log.trace(e)
delegate.foreach(_.uncaughtException(t, e))
}
+
}
diff --git a/server/src/main/scala/com.olegych.scastie.web/PlayJsonSupport.scala b/server/src/main/scala/com.olegych.scastie.web/PlayJsonSupport.scala
index dc091956a..efc849679 100644
--- a/server/src/main/scala/com.olegych.scastie.web/PlayJsonSupport.scala
+++ b/server/src/main/scala/com.olegych.scastie.web/PlayJsonSupport.scala
@@ -16,6 +16,8 @@
package com.olegych.scastie.web
+import scala.collection.immutable.Seq
+
import akka.http.scaladsl.marshalling.{Marshaller, ToEntityMarshaller}
import akka.http.scaladsl.model.ContentTypeRange
import akka.http.scaladsl.model.MediaTypes.`application/json`
@@ -23,66 +25,65 @@ import akka.http.scaladsl.server.{RejectionError, ValidationRejection}
import akka.http.scaladsl.unmarshalling.{FromEntityUnmarshaller, Unmarshaller}
import akka.util.ByteString
import play.api.libs.json.{JsError, JsValue, Json, Reads, Writes}
-import scala.collection.immutable.Seq
/**
- * Automatic to and from JSON marshalling/unmarshalling using an in-scope *play-json* protocol.
- */
+ * Automatic to and from JSON marshalling/unmarshalling using an in-scope *play-json* protocol.
+ */
object PlayJsonSupport extends PlayJsonSupport {
final case class PlayJsonError(error: JsError) extends RuntimeException {
- override def getMessage: String =
- JsError.toJson(error).toString()
+ override def getMessage: String = JsError.toJson(error).toString()
}
+
}
/**
- * Automatic to and from JSON marshalling/unmarshalling using an in-scope *play-json* protocol.
- */
+ * Automatic to and from JSON marshalling/unmarshalling using an in-scope *play-json* protocol.
+ */
trait PlayJsonSupport {
import PlayJsonSupport._
- def unmarshallerContentTypes: Seq[ContentTypeRange] =
- List(`application/json`)
+ def unmarshallerContentTypes: Seq[ContentTypeRange] = List(`application/json`)
- private val jsonStringUnmarshaller =
- Unmarshaller.byteStringUnmarshaller
- .forContentTypes(unmarshallerContentTypes: _*)
- .mapWithCharset {
- case (ByteString.empty, _) => throw Unmarshaller.NoContentException
- case (data, charset) => data.decodeString(charset.nioCharset.name)
- }
+ private val jsonStringUnmarshaller = Unmarshaller.byteStringUnmarshaller
+ .forContentTypes(unmarshallerContentTypes: _*)
+ .mapWithCharset {
+ case (ByteString.empty, _) => throw Unmarshaller.NoContentException
+ case (data, charset) => data.decodeString(charset.nioCharset.name)
+ }
- private val jsonStringMarshaller =
- Marshaller.stringMarshaller(`application/json`)
+ private val jsonStringMarshaller = Marshaller.stringMarshaller(`application/json`)
/**
- * HTTP entity => `A`
- *
- * @tparam A type to decode
- * @return unmarshaller for `A`
- */
+ * HTTP entity => `A`
+ *
+ * @tparam A
+ * type to decode
+ * @return
+ * unmarshaller for `A`
+ */
implicit def unmarshaller[A: Reads]: FromEntityUnmarshaller[A] = {
- def read(json: JsValue) =
- implicitly[Reads[A]]
- .reads(json)
- .recoverTotal { e =>
- throw RejectionError(
- ValidationRejection(JsError.toJson(e).toString, Some(PlayJsonError(e)))
- )
- }
+ def read(json: JsValue) = implicitly[Reads[A]]
+ .reads(json)
+ .recoverTotal { e =>
+ throw RejectionError(
+ ValidationRejection(JsError.toJson(e).toString, Some(PlayJsonError(e)))
+ )
+ }
jsonStringUnmarshaller.map(data => read(Json.parse(data)))
}
/**
- * `A` => HTTP entity
- *
- * @tparam A type to encode
- * @return marshaller for any `A` value
- */
+ * `A` => HTTP entity
+ *
+ * @tparam A
+ * type to encode
+ * @return
+ * marshaller for any `A` value
+ */
implicit def marshaller[A](
- implicit writes: Writes[A],
- printer: JsValue => String = Json.prettyPrint
- ): ToEntityMarshaller[A] =
- jsonStringMarshaller.compose(printer).compose(writes.writes)
+ implicit writes: Writes[A],
+ printer: JsValue => String = Json.prettyPrint
+ ): ToEntityMarshaller[A] = jsonStringMarshaller.compose(printer).compose(writes.writes)
+
}
diff --git a/server/src/main/scala/com.olegych.scastie.web/RestApiServer.scala b/server/src/main/scala/com.olegych.scastie.web/RestApiServer.scala
index cb83593d2..80a1e8e99 100644
--- a/server/src/main/scala/com.olegych.scastie.web/RestApiServer.scala
+++ b/server/src/main/scala/com.olegych.scastie.web/RestApiServer.scala
@@ -1,29 +1,28 @@
package com.olegych.scastie
package web
-import api._
-import balancer._
+import scala.concurrent.{ExecutionContext, Future}
+import scala.concurrent.duration.DurationInt
-import akka.pattern.ask
import akka.actor.ActorRef
-import akka.util.Timeout
import akka.http.scaladsl.model.RemoteAddress
-
-import scala.concurrent.{Future, ExecutionContext}
-import scala.concurrent.duration.DurationInt
+import akka.pattern.ask
+import akka.util.Timeout
+import api._
+import balancer._
import com.olegych.scastie.storage.PolicyAcceptance
class RestApiServer(
- dispatchActor: ActorRef,
- ip: RemoteAddress,
- maybeUser: Option[User]
-)(implicit executionContext: ExecutionContext)
- extends RestApi {
+ dispatchActor: ActorRef,
+ ip: RemoteAddress,
+ maybeUser: Option[User]
+)(
+ implicit executionContext: ExecutionContext
+) extends RestApi {
implicit val timeout: Timeout = Timeout(20.seconds)
- private def wrap(inputs: Inputs): InputsWithIpAndUser =
- InputsWithIpAndUser(inputs, UserTrace(ip.toString, maybeUser))
+ private def wrap(inputs: Inputs): InputsWithIpAndUser = InputsWithIpAndUser(inputs, UserTrace(ip.toString, maybeUser))
def run(inputs: Inputs): Future[SnippetId] = {
dispatchActor
@@ -90,8 +89,7 @@ class RestApiServer(
def fetchUserSnippets(): Future[List[SnippetSummary]] = {
maybeUser match {
- case Some(user) =>
- dispatchActor
+ case Some(user) => dispatchActor
.ask(FetchUserSnippets(user))
.mapTo[List[SnippetSummary]]
case _ => Future.successful(Nil)
@@ -101,8 +99,7 @@ class RestApiServer(
@deprecated("Scheduled for removal", "2023-04-30")
def getPrivacyPolicy(): Future[Boolean] = {
maybeUser match {
- case Some(user) =>
- dispatchActor
+ case Some(user) => dispatchActor
.ask(GetPrivacyPolicy(user))
.mapTo[Boolean]
case _ => Future.successful(true)
@@ -112,8 +109,7 @@ class RestApiServer(
@deprecated("Scheduled for removal", "2023-04-30")
def acceptPrivacyPolicy(): Future[Boolean] = {
maybeUser match {
- case Some(user) =>
- dispatchActor
+ case Some(user) => dispatchActor
.ask(SetPrivacyPolicy(user, true))
.mapTo[Boolean]
case _ => Future.successful(true)
@@ -123,8 +119,7 @@ class RestApiServer(
@deprecated("Scheduled for removal", "2023-04-30")
def removeUserFromPolicyStatus(): Future[Boolean] = {
maybeUser match {
- case Some(user) =>
- dispatchActor
+ case Some(user) => dispatchActor
.ask(RemovePrivacyPolicy(user))
.mapTo[Boolean]
case _ => Future.successful(true)
@@ -134,11 +129,11 @@ class RestApiServer(
@deprecated("Scheduled for removal", "2023-04-30")
def removeAllUserSnippets(): Future[Boolean] = {
maybeUser match {
- case Some(user) =>
- dispatchActor
+ case Some(user) => dispatchActor
.ask(RemoveAllUserSnippets(user))
.mapTo[Boolean]
case _ => Future.successful(true)
}
}
+
}
diff --git a/server/src/main/scala/com.olegych.scastie.web/ServerMain.scala b/server/src/main/scala/com.olegych.scastie.web/ServerMain.scala
index d13b7c273..3b15085e3 100644
--- a/server/src/main/scala/com.olegych.scastie.web/ServerMain.scala
+++ b/server/src/main/scala/com.olegych.scastie.web/ServerMain.scala
@@ -1,5 +1,10 @@
package com.olegych.scastie.web
+import scala.concurrent.duration._
+import scala.concurrent.Await
+import scala.util.Failure
+import scala.util.Success
+
import akka.actor.ActorSystem
import akka.actor.Props
import akka.http.scaladsl._
@@ -11,12 +16,6 @@ import com.olegych.scastie.web.oauth2._
import com.olegych.scastie.web.routes._
import com.typesafe.config.ConfigFactory
import com.typesafe.scalalogging.Logger
-
-import scala.concurrent.Await
-import scala.concurrent.duration._
-import scala.util.Failure
-import scala.util.Success
-
import server.Directives._
object ServerMain {
@@ -77,8 +76,7 @@ object ServerMain {
val scalaLangRoutes = new ScalaLangRoutes(dispatchActor, userDirectives).routes
val frontPageRoutes = new FrontPageRoutes(dispatchActor, production, hostname).routes
- val routes =
- oauthRoutes ~
+ val routes = oauthRoutes ~
cors() {
pathPrefix("api") {
apiRoutes ~
diff --git a/server/src/main/scala/com.olegych.scastie.web/oauth2/Github.scala b/server/src/main/scala/com.olegych.scastie.web/oauth2/Github.scala
index 6c438b26e..fa5d45a04 100644
--- a/server/src/main/scala/com.olegych.scastie.web/oauth2/Github.scala
+++ b/server/src/main/scala/com.olegych.scastie.web/oauth2/Github.scala
@@ -1,33 +1,35 @@
package com.olegych.scastie.web.oauth2
+import scala.concurrent.Future
+
import akka.actor.ActorSystem
import akka.http.scaladsl._
-import akka.http.scaladsl.model.HttpMethods.POST
-import akka.http.scaladsl.model.Uri._
import akka.http.scaladsl.model._
import akka.http.scaladsl.model.headers._
+import akka.http.scaladsl.model.HttpMethods.POST
+import akka.http.scaladsl.model.Uri._
import akka.http.scaladsl.unmarshalling.Unmarshal
import com.olegych.scastie.api.User
import com.olegych.scastie.web.PlayJsonSupport
import com.typesafe.config.ConfigFactory
-import scala.concurrent.Future
-
case class AccessToken(access_token: String)
-class Github(implicit system: ActorSystem) extends PlayJsonSupport {
+class Github(
+ implicit system: ActorSystem
+) extends PlayJsonSupport {
import play.api.libs.json._
import system.dispatcher
- implicit val formatUser: OFormat[User] = Json.format[User]
+ implicit val formatUser: OFormat[User] = Json.format[User]
implicit val readAccessToken: Reads[AccessToken] = Json.reads[AccessToken]
- private val config =
- ConfigFactory.load().getConfig("com.olegych.scastie.web.oauth2")
- val clientId: String = config.getString("client-id")
+ private val config = ConfigFactory.load().getConfig("com.olegych.scastie.web.oauth2")
+ val clientId: String = config.getString("client-id")
private val clientSecret = config.getString("client-secret")
- private val redirectUri = config.getString("uri") + "/callback"
+ private val redirectUri = config.getString("uri") + "/callback"
def getUserWithToken(token: String): Future[User] = info(token)
+
def getUserWithOauth2(code: String): Future[User] = {
def access = {
Http()
@@ -36,18 +38,16 @@ class Github(implicit system: ActorSystem) extends PlayJsonSupport {
method = POST,
uri = Uri("https://github.com/login/oauth/access_token").withQuery(
Query(
- "client_id" -> clientId,
+ "client_id" -> clientId,
"client_secret" -> clientSecret,
- "code" -> code,
- "redirect_uri" -> redirectUri
+ "code" -> code,
+ "redirect_uri" -> redirectUri
)
),
headers = List(Accept(MediaTypes.`application/json`))
)
)
- .flatMap(
- response => Unmarshal(response).to[AccessToken].map(_.access_token)
- )
+ .flatMap(response => Unmarshal(response).to[AccessToken].map(_.access_token))
}
access.flatMap(info)
@@ -65,4 +65,5 @@ class Github(implicit system: ActorSystem) extends PlayJsonSupport {
.singleRequest(fetchGithub(Path.Empty / "user"))
.flatMap(response => Unmarshal(response).to[User])
}
+
}
diff --git a/server/src/main/scala/com.olegych.scastie.web/oauth2/GithubUserSession.scala b/server/src/main/scala/com.olegych.scastie.web/oauth2/GithubUserSession.scala
index 1e76612d5..6ed2e6c8a 100644
--- a/server/src/main/scala/com.olegych.scastie.web/oauth2/GithubUserSession.scala
+++ b/server/src/main/scala/com.olegych.scastie.web/oauth2/GithubUserSession.scala
@@ -3,6 +3,10 @@ package com.olegych.scastie.web.oauth2
import java.lang.System.{lineSeparator => nl}
import java.nio.file._
import java.util.UUID
+import scala.collection.concurrent.TrieMap
+import scala.jdk.CollectionConverters._
+import scala.util.control.NonFatal
+import scala.util.Try
import akka.actor.ActorSystem
import com.olegych.scastie.api.User
@@ -11,23 +15,14 @@ import com.typesafe.config.ConfigFactory
import com.typesafe.scalalogging.Logger
import play.api.libs.json.Json
-import scala.collection.concurrent.TrieMap
-import scala.jdk.CollectionConverters._
-import scala.util.Try
-import scala.util.control.NonFatal
-
class GithubUserSession(system: ActorSystem) {
val logger = Logger("GithubUserSession")
- private val configuration =
- ConfigFactory.load().getConfig("com.olegych.scastie.web")
- private val usersFile =
- Paths.get(configuration.getString("oauth2.users-file"))
- private val usersSessions =
- Paths.get(configuration.getString("oauth2.sessions-file"))
+ private val configuration = ConfigFactory.load().getConfig("com.olegych.scastie.web")
+ private val usersFile = Paths.get(configuration.getString("oauth2.users-file"))
+ private val usersSessions = Paths.get(configuration.getString("oauth2.sessions-file"))
- private val sessionConfig =
- SessionConfig.default(configuration.getString("session-secret"))
+ private val sessionConfig = SessionConfig.default(configuration.getString("session-secret"))
private lazy val users = {
val trie = TrieMap[UUID, User]()
@@ -35,12 +30,12 @@ class GithubUserSession(system: ActorSystem) {
trie
}
- implicit def serializer: SessionSerializer[UUID, String] =
- new SingleValueSessionSerializer(
- _.toString(),
- (id: String) => Try { UUID.fromString(id) }
- )
- implicit val sessionManager = new SessionManager[UUID](sessionConfig)
+ implicit def serializer: SessionSerializer[UUID, String] = new SingleValueSessionSerializer(
+ _.toString(),
+ (id: String) => Try { UUID.fromString(id) }
+ )
+
+ implicit val sessionManager = new SessionManager[UUID](sessionConfig)
implicit val refreshTokenStorage = new ActorRefreshTokenStorage(system)
private def readSessionsFile(): Vector[(UUID, User)] = {
@@ -64,7 +59,7 @@ class GithubUserSession(system: ActorSystem) {
private def appendSessionsFile(uuid: UUID, user: User): Unit = synchronized {
val pair = uuid -> user
users += pair
- val sessions = readSessionsFile()
+ val sessions = readSessionsFile()
val sessions0 = sessions :+ pair
if (Files.exists(usersSessions)) {
@@ -98,6 +93,5 @@ class GithubUserSession(system: ActorSystem) {
}
}
- def getUser(id: Option[UUID]): Option[User] =
- id.flatMap(users.get)
+ def getUser(id: Option[UUID]): Option[User] = id.flatMap(users.get)
}
diff --git a/server/src/main/scala/com.olegych.scastie.web/oauth2/InMemoryRefreshTokenStorage.scala b/server/src/main/scala/com.olegych.scastie.web/oauth2/InMemoryRefreshTokenStorage.scala
index 18212e147..6fafaf440 100644
--- a/server/src/main/scala/com.olegych.scastie.web/oauth2/InMemoryRefreshTokenStorage.scala
+++ b/server/src/main/scala/com.olegych.scastie.web/oauth2/InMemoryRefreshTokenStorage.scala
@@ -1,22 +1,21 @@
package com.olegych.scastie.web.oauth2
-import com.softwaremill.session.{RefreshTokenData, RefreshTokenStorage, RefreshTokenLookupResult}
+import java.util.UUID
+import scala.collection.mutable
+import scala.concurrent.duration._
+import scala.concurrent.Future
+
import akka.actor.{Actor, ActorSystem, Props}
import akka.pattern.ask
import akka.util.Timeout
-
-import scala.concurrent.Future
-import scala.concurrent.duration._
-import scala.collection.mutable
-
-import java.util.UUID
+import com.softwaremill.session.{RefreshTokenData, RefreshTokenLookupResult, RefreshTokenStorage}
private[oauth2] case class SessionStorage(session: UUID, tokenHash: String, expires: Long)
class ActorRefreshTokenStorage(system: ActorSystem) extends RefreshTokenStorage[UUID] {
import system.dispatcher
implicit private val timeout = Timeout(10.seconds)
- private val impl = system.actorOf(Props(new ActorRefreshTokenStorageImpl()))
+ private val impl = system.actorOf(Props(new ActorRefreshTokenStorageImpl()))
def lookup(selector: String): Future[Option[RefreshTokenLookupResult[UUID]]] =
(impl ? Lookup(selector)).mapTo[Option[RefreshTokenLookupResult[UUID]]]
@@ -25,16 +24,19 @@ class ActorRefreshTokenStorage(system: ActorSystem) extends RefreshTokenStorage[
impl ! Store(data)
Future.successful(())
}
+
def remove(selector: String): Future[Unit] = {
impl ! Remove(selector)
Future.successful(())
}
+
def schedule[S](after: Duration)(op: => Future[S]): Unit = {
after match {
case finite: FiniteDuration => system.scheduler.scheduleOnce(finite)(op)
case _: Duration.Infinite => ()
}
}
+
}
private[oauth2] case class Lookup(selector: String)
@@ -43,18 +45,15 @@ private[oauth2] case class Remove(selector: String)
class ActorRefreshTokenStorageImpl() extends Actor {
private val storage = mutable.Map[String, SessionStorage]()
+
override def receive: Receive = {
case Lookup(selector) =>
- val lookupResult =
- storage
- .get(selector)
- .map(
- s => RefreshTokenLookupResult(s.tokenHash, s.expires, () => s.session)
- )
+ val lookupResult = storage
+ .get(selector)
+ .map(s => RefreshTokenLookupResult(s.tokenHash, s.expires, () => s.session))
sender() ! lookupResult
- case Store(data) =>
- storage.put(data.selector, SessionStorage(data.forSession, data.tokenHash, data.expires))
- case Remove(selector) =>
- storage.remove(selector)
+ case Store(data) => storage.put(data.selector, SessionStorage(data.forSession, data.tokenHash, data.expires))
+ case Remove(selector) => storage.remove(selector)
}
+
}
diff --git a/server/src/main/scala/com.olegych.scastie.web/oauth2/UserDirectives.scala b/server/src/main/scala/com.olegych.scastie.web/oauth2/UserDirectives.scala
index bf7bfe263..5d8748163 100644
--- a/server/src/main/scala/com.olegych.scastie.web/oauth2/UserDirectives.scala
+++ b/server/src/main/scala/com.olegych.scastie.web/oauth2/UserDirectives.scala
@@ -1,21 +1,20 @@
package com.olegych.scastie.web.oauth2
-import com.olegych.scastie.api.User
+import scala.concurrent.ExecutionContext
import akka.http.scaladsl._
-import server._
-
+import com.olegych.scastie.api.User
import com.softwaremill.session._
+import server._
import SessionDirectives._
import SessionOptions._
-import scala.concurrent.ExecutionContext
-
class UserDirectives(
- session: GithubUserSession
-)(implicit val executionContext: ExecutionContext) {
+ session: GithubUserSession
+)(
+ implicit val executionContext: ExecutionContext
+) {
import session._
- def optionalLogin: Directive1[Option[User]] =
- optionalSession(refreshable, usingCookies).map(getUser)
+ def optionalLogin: Directive1[Option[User]] = optionalSession(refreshable, usingCookies).map(getUser)
}
diff --git a/server/src/main/scala/com.olegych.scastie.web/routes/ApiRoutes.scala b/server/src/main/scala/com.olegych.scastie.web/routes/ApiRoutes.scala
index 4709d7685..8e987b473 100644
--- a/server/src/main/scala/com.olegych.scastie.web/routes/ApiRoutes.scala
+++ b/server/src/main/scala/com.olegych.scastie.web/routes/ApiRoutes.scala
@@ -2,95 +2,81 @@ package com.olegych.scastie.web.routes
import akka.actor.{ActorRef, ActorSystem}
import akka.http.scaladsl.coding.Coders.Gzip
-import akka.http.scaladsl.server.Directives._
import akka.http.scaladsl.server.{Directive1, Route}
+import akka.http.scaladsl.server.Directives._
import com.olegych.scastie.api._
import com.olegych.scastie.web._
import com.olegych.scastie.web.oauth2._
class ApiRoutes(
- dispatchActor: ActorRef,
- userDirectives: UserDirectives
-)(implicit system: ActorSystem)
- extends PlayJsonSupport {
+ dispatchActor: ActorRef,
+ userDirectives: UserDirectives
+)(
+ implicit system: ActorSystem
+) extends PlayJsonSupport {
import system.dispatcher
import userDirectives.optionalLogin
- val withRestApiServer: Directive1[RestApiServer] =
- (extractClientIP & optionalLogin).tmap {
- case (remoteAddress, user) =>
- new RestApiServer(dispatchActor, remoteAddress, user)
- }
+ val withRestApiServer: Directive1[RestApiServer] = (extractClientIP & optionalLogin).tmap {
+ case (remoteAddress, user) => new RestApiServer(dispatchActor, remoteAddress, user)
+ }
- val routes: Route =
- withRestApiServer(
- server =>
+ val routes: Route = withRestApiServer(server =>
+ concat(
+ post(
concat(
- post(
- concat(
- path("run")(
- entity(as[Inputs])(inputs => complete(server.run(inputs)))
- ),
- path("save")(
- entity(as[Inputs])(inputs => complete(server.save(inputs)))
- ),
- path("update")(
- entity(as[EditInputs])(
- editInputs => complete(server.update(editInputs))
- )
- ),
- path("fork")(
- entity(as[EditInputs])(
- editInputs => complete(server.fork(editInputs))
- )
- ),
- path("delete")(
- entity(as[SnippetId])(
- snippetId => complete(server.delete(snippetId))
- )
- ),
- path("format")(
- entity(as[FormatRequest])(
- request => complete(server.format(request))
- )
- )
- )
+ path("run")(
+ entity(as[Inputs])(inputs => complete(server.run(inputs)))
),
- encodeResponseWith(Gzip)(
- get(
- concat(
- snippetIdStart("snippets")(
- sid => complete(server.fetch(sid))
- ),
- path("old-snippets" / IntNumber)(
- id => complete(server.fetchOld(id))
- ),
- path("user" / "settings")(
- complete(server.fetchUser())
- ),
- path("user" / "snippets")(
- complete(server.fetchUserSnippets())
- )
- )
- )
+ path("save")(
+ entity(as[Inputs])(inputs => complete(server.save(inputs)))
+ ),
+ path("update")(
+ entity(as[EditInputs])(editInputs => complete(server.update(editInputs)))
),
- post(
+ path("fork")(
+ entity(as[EditInputs])(editInputs => complete(server.fork(editInputs)))
+ ),
+ path("delete")(
+ entity(as[SnippetId])(snippetId => complete(server.delete(snippetId)))
+ ),
+ path("format")(
+ entity(as[FormatRequest])(request => complete(server.format(request)))
+ )
+ )
+ ),
+ encodeResponseWith(Gzip)(
+ get(
concat(
- path("user" / "privacyPolicyStatus")(
- complete(server.getPrivacyPolicy())
- ),
- path("user" / "acceptPrivacyPolicy")(
- complete(server.acceptPrivacyPolicy())
- ),
- path("user" / "removeUserFromPolicyStatus")(
- complete(server.removeUserFromPolicyStatus())
- ),
- path("user" / "removeAllUserSnippets")(
- complete(server.removeAllUserSnippets())
+ snippetIdStart("snippets")(sid => complete(server.fetch(sid))),
+ path("old-snippets" / IntNumber)(id => complete(server.fetchOld(id))),
+ path("user" / "settings")(
+ complete(server.fetchUser())
),
+ path("user" / "snippets")(
+ complete(server.fetchUserSnippets())
+ )
+ )
+ )
+ ),
+ post(
+ concat(
+ path("user" / "privacyPolicyStatus")(
+ complete(server.getPrivacyPolicy())
+ ),
+ path("user" / "acceptPrivacyPolicy")(
+ complete(server.acceptPrivacyPolicy())
+ ),
+ path("user" / "removeUserFromPolicyStatus")(
+ complete(server.removeUserFromPolicyStatus())
+ ),
+ path("user" / "removeAllUserSnippets")(
+ complete(server.removeAllUserSnippets())
)
)
)
)
+ )
+
}
diff --git a/server/src/main/scala/com.olegych.scastie.web/routes/DownloadRoutes.scala b/server/src/main/scala/com.olegych.scastie.web/routes/DownloadRoutes.scala
index e36216679..08bedcae0 100644
--- a/server/src/main/scala/com.olegych.scastie.web/routes/DownloadRoutes.scala
+++ b/server/src/main/scala/com.olegych.scastie.web/routes/DownloadRoutes.scala
@@ -1,33 +1,27 @@
package com.olegych.scastie.web.routes
-import com.olegych.scastie.balancer.DownloadSnippet
+import java.nio.file.Path
+import scala.concurrent.duration.DurationInt
+import akka.actor.ActorRef
import akka.http.scaladsl.server.Directives._
import akka.http.scaladsl.server.Route
-
-import akka.actor.ActorRef
import akka.pattern.ask
-
-import java.nio.file.Path
-
import akka.util.Timeout
-import scala.concurrent.duration.DurationInt
+import com.olegych.scastie.balancer.DownloadSnippet
class DownloadRoutes(dispatchActor: ActorRef) {
implicit val timeout = Timeout(5.seconds)
- val routes: Route =
- get {
- snippetIdStart("download")(
- sid =>
- onSuccess((dispatchActor ? DownloadSnippet(sid)).mapTo[Option[Path]]) {
- case Some(path) =>
- getFromFile(path.toFile)
- case None =>
- throw new Exception(
- s"Can't serve project ${sid.base64UUID} to user ${sid.user.getOrElse("anon")}"
- )
- }
- )
- }
+ val routes: Route = get {
+ snippetIdStart("download")(sid =>
+ onSuccess((dispatchActor ? DownloadSnippet(sid)).mapTo[Option[Path]]) {
+ case Some(path) => getFromFile(path.toFile)
+ case None => throw new Exception(
+ s"Can't serve project ${sid.base64UUID} to user ${sid.user.getOrElse("anon")}"
+ )
+ }
+ )
+ }
+
}
diff --git a/server/src/main/scala/com.olegych.scastie.web/routes/FrontPageRoutes.scala b/server/src/main/scala/com.olegych.scastie.web/routes/FrontPageRoutes.scala
index 9dc69654b..1b56736ff 100644
--- a/server/src/main/scala/com.olegych.scastie.web/routes/FrontPageRoutes.scala
+++ b/server/src/main/scala/com.olegych.scastie.web/routes/FrontPageRoutes.scala
@@ -1,17 +1,21 @@
package com.olegych.scastie.web.routes
+import scala.concurrent.duration.DurationInt
+import scala.concurrent.ExecutionContext
+import scala.concurrent.Future
+
import akka.actor.ActorRef
import akka.http.scaladsl.coding.Coders.Gzip
import akka.http.scaladsl.coding.Coders.NoCoding
-import akka.http.scaladsl.model.HttpEntity
import akka.http.scaladsl.model._
import akka.http.scaladsl.model.headers.{`Cache-Control`, CacheDirectives}
+import akka.http.scaladsl.model.HttpEntity
import akka.http.scaladsl.server.Directives._
import akka.http.scaladsl.server.Route
import akka.http.scaladsl.server.RouteResult
import akka.pattern.ask
-import akka.stream.Materializer
import akka.stream.scaladsl.StreamConverters
+import akka.stream.Materializer
import akka.util.ByteString
import akka.util.Timeout
import com.olegych.scastie.api.FetchResult
@@ -21,27 +25,29 @@ import com.olegych.scastie.balancer.FetchSnippet
import com.olegych.scastie.util.Base64UUID
import org.apache.commons.text.StringEscapeUtils
-import scala.concurrent.ExecutionContext
-import scala.concurrent.Future
-import scala.concurrent.duration.DurationInt
-
-class FrontPageRoutes(dispatchActor: ActorRef, production: Boolean, hostname: String)(implicit ec: ExecutionContext, mat: Materializer) {
+class FrontPageRoutes(dispatchActor: ActorRef, production: Boolean, hostname: String)(
+ implicit ec: ExecutionContext,
+ mat: Materializer
+) {
implicit val timeout: Timeout = Timeout(20.seconds)
+
private val placeholders = List(
- "Scastie can run any Scala program with any library in your browser. You don’t need to download or install anything.",
+ "Scastie can run any Scala program with any library in your browser. You don’t need to download or install anything."
)
private val indexResource = "public/index.html"
- private val indexResourceContent = Future.traverse(Option(getClass.getClassLoader.getResource(indexResource)).toList) { url =>
- StreamConverters.fromInputStream(() => url.openStream()).runFold("")(_ + _.utf8String)
- }
+
+ private val indexResourceContent =
+ Future.traverse(Option(getClass.getClassLoader.getResource(indexResource)).toList) { url =>
+ StreamConverters.fromInputStream(() => url.openStream()).runFold("")(_ + _.utf8String)
+ }
+
private val index = getFromResource(indexResource)
private def embeddedResource(snippetId: SnippetId, theme: Option[String]): String = {
val user = snippetId.user match {
- case Some(SnippetUserPart(login, update)) =>
- s"user: '$login', update: $update,"
- case None => ""
+ case Some(SnippetUserPart(login, update)) => s"user: '$login', update: $update,"
+ case None => ""
}
val themePart = theme match {
@@ -78,26 +84,33 @@ class FrontPageRoutes(dispatchActor: ActorRef, production: Boolean, hostname: St
respondWithHeader(`Cache-Control`(CacheDirectives.`no-cache`))(
concat(
path("embedded.js")(
- getFromResource("public/embedded/embedded.js", ContentType(MediaTypes.`application/javascript`, HttpCharsets.`UTF-8`))
+ getFromResource(
+ "public/embedded/embedded.js",
+ ContentType(MediaTypes.`application/javascript`, HttpCharsets.`UTF-8`)
+ )
),
path("public" / "embedded.css")(
getFromResource("public/embedded/style.css", ContentType(MediaTypes.`text/css`, HttpCharsets.`UTF-8`))
),
path("public" / "tree-sitter.wasm")(
- getFromResource("public/tree-sitter.wasm", ContentType(MediaType.applicationBinary("wasm", MediaType.Compressible, "wasm")))
+ getFromResource(
+ "public/tree-sitter.wasm",
+ ContentType(MediaType.applicationBinary("wasm", MediaType.Compressible, "wasm"))
+ )
),
path("public" / "tree-sitter-scala.wasm")(
- getFromResource("public/tree-sitter-scala.wasm", ContentType(MediaType.applicationBinary("wasm", MediaType.Compressible, "wasm")))
+ getFromResource(
+ "public/tree-sitter-scala.wasm",
+ ContentType(MediaType.applicationBinary("wasm", MediaType.Compressible, "wasm"))
+ )
),
path("public" / "highlights.scm")(
getFromResource("public/highlights.scm", ContentType(MediaTypes.`text/css`, HttpCharsets.`UTF-8`))
- ),
+ )
)
),
respondWithHeader(`Cache-Control`(CacheDirectives.immutableDirective))(
- path("public" / Remaining)(
- path => getFromResource("public/" + path)
- ),
+ path("public" / Remaining)(path => getFromResource("public/" + path))
),
pathSingleSlash(index),
snippetId { snippetId => ctx =>
@@ -105,30 +118,35 @@ class FrontPageRoutes(dispatchActor: ActorRef, production: Boolean, hostname: St
s <- dispatchActor.ask(FetchSnippet(snippetId)).mapTo[Option[FetchResult]]
c <- indexResourceContent
r <- index(ctx)
- } yield
- (r, c, s) match {
- case (r: RouteResult.Complete, List(c), Some(s)) if r.response.status.intValue() == 200 =>
- val code = StringEscapeUtils.escapeHtml4(s.inputs.code)
- r.copy(
- response = r.response.withEntity(
- HttpEntity.Strict(
- r.response.entity.contentType,
- ByteString.fromString(placeholders.foldLeft(c)(_.replace(_, code))),
- )
+ } yield (r, c, s) match {
+ case (r: RouteResult.Complete, List(c), Some(s)) if r.response.status.intValue() == 200 =>
+ val code = StringEscapeUtils.escapeHtml4(s.inputs.code)
+ r.copy(
+ response = r.response.withEntity(
+ HttpEntity.Strict(
+ r.response.entity.contentType,
+ ByteString.fromString(placeholders.foldLeft(c)(_.replace(_, code)))
)
)
- case _ => r
- }
+ )
+ case _ => r
+ }
},
parameter("theme".?) { theme =>
snippetIdExtension(".js") { sid =>
complete {
- HttpResponse(entity = HttpEntity(ContentType(MediaTypes.`application/javascript`, HttpCharsets.`UTF-8`), embeddedResource(sid, theme)))
+ HttpResponse(entity =
+ HttpEntity(
+ ContentType(MediaTypes.`application/javascript`, HttpCharsets.`UTF-8`),
+ embeddedResource(sid, theme)
+ )
+ )
}
}
},
- index,
+ index
)
)
)
+
}
diff --git a/server/src/main/scala/com.olegych.scastie.web/routes/OAuth2Routes.scala b/server/src/main/scala/com.olegych.scastie.web/routes/OAuth2Routes.scala
index 755c91c75..f8dda92c9 100644
--- a/server/src/main/scala/com.olegych.scastie.web/routes/OAuth2Routes.scala
+++ b/server/src/main/scala/com.olegych.scastie.web/routes/OAuth2Routes.scala
@@ -2,85 +2,82 @@ package com.olegych.scastie
package web
package routes
-import oauth2._
-
-import com.softwaremill.session.SessionDirectives._
-import com.softwaremill.session.SessionOptions._
-import com.softwaremill.session.CsrfDirectives._
-import com.softwaremill.session.CsrfOptions._
+import scala.concurrent.ExecutionContext
import akka.http.scaladsl.model._
-import akka.http.scaladsl.model.Uri.Query
-import akka.http.scaladsl.model.StatusCodes.TemporaryRedirect
import akka.http.scaladsl.model.headers.Referer
+import akka.http.scaladsl.model.StatusCodes.TemporaryRedirect
+import akka.http.scaladsl.model.Uri.Query
import akka.http.scaladsl.server.Directives._
import akka.http.scaladsl.server.Route
-
-import scala.concurrent.ExecutionContext
+import com.softwaremill.session.CsrfDirectives._
+import com.softwaremill.session.CsrfOptions._
+import com.softwaremill.session.SessionDirectives._
+import com.softwaremill.session.SessionOptions._
+import oauth2._
class OAuth2Routes(github: Github, session: GithubUserSession)(
- implicit val executionContext: ExecutionContext
+ implicit val executionContext: ExecutionContext
) {
import session._
- val routes: Route =
- get(
- concat(
- path("login") {
- parameter("home".?)(
- home =>
- optionalHeaderValueByType[Referer](()) { referrer =>
- redirect(
- Uri("https://github.com/login/oauth/authorize").withQuery(
- Query(
- "client_id" -> github.clientId,
- "state" -> {
- val homeUri = "/"
- if (home.isDefined) homeUri
- else referrer.map(_.value).getOrElse(homeUri)
- }
- )
- ),
- TemporaryRedirect
+ val routes: Route = get(
+ concat(
+ path("login") {
+ parameter("home".?)(home =>
+ optionalHeaderValueByType[Referer](()) { referrer =>
+ redirect(
+ Uri("https://github.com/login/oauth/authorize").withQuery(
+ Query(
+ "client_id" -> github.clientId,
+ "state" -> {
+ val homeUri = "/"
+ if (home.isDefined) homeUri
+ else referrer.map(_.value).getOrElse(homeUri)
+ }
)
- }
- )
- },
- path("logout") {
- headerValueByType[Referer](()) { referrer =>
- requiredSession(refreshable, usingCookies) { _ =>
- invalidateSession(refreshable, usingCookies) { ctx =>
- ctx.complete(
- HttpResponse(
- status = TemporaryRedirect,
- headers = headers.Location(Uri(referrer.value)) :: Nil,
- entity = HttpEntity.Empty
- )
+ ),
+ TemporaryRedirect
+ )
+ }
+ )
+ },
+ path("logout") {
+ headerValueByType[Referer](()) { referrer =>
+ requiredSession(refreshable, usingCookies) { _ =>
+ invalidateSession(refreshable, usingCookies) { ctx =>
+ ctx.complete(
+ HttpResponse(
+ status = TemporaryRedirect,
+ headers = headers.Location(Uri(referrer.value)) :: Nil,
+ entity = HttpEntity.Empty
)
- }
+ )
}
}
- },
- pathPrefix("callback") {
- pathEnd {
- parameters("code", "state".?) { (code, state) =>
- onSuccess(github.getUserWithOauth2(code)) { user =>
- setSession(refreshable, usingCookies, session.addUser(user)) {
- setNewCsrfToken(checkHeader) { ctx =>
- ctx.complete(
- HttpResponse(
- status = TemporaryRedirect,
- headers = headers
- .Location(Uri(state.getOrElse("/"))) :: Nil,
- entity = HttpEntity.Empty
- )
+ }
+ },
+ pathPrefix("callback") {
+ pathEnd {
+ parameters("code", "state".?) { (code, state) =>
+ onSuccess(github.getUserWithOauth2(code)) { user =>
+ setSession(refreshable, usingCookies, session.addUser(user)) {
+ setNewCsrfToken(checkHeader) { ctx =>
+ ctx.complete(
+ HttpResponse(
+ status = TemporaryRedirect,
+ headers = headers
+ .Location(Uri(state.getOrElse("/"))) :: Nil,
+ entity = HttpEntity.Empty
)
- }
+ )
}
}
}
}
}
- )
+ }
)
+ )
+
}
diff --git a/server/src/main/scala/com.olegych.scastie.web/routes/ProgressRoutes.scala b/server/src/main/scala/com.olegych.scastie.web/routes/ProgressRoutes.scala
index 742dbf75e..c0096fbde 100644
--- a/server/src/main/scala/com.olegych.scastie.web/routes/ProgressRoutes.scala
+++ b/server/src/main/scala/com.olegych.scastie.web/routes/ProgressRoutes.scala
@@ -1,6 +1,8 @@
package com.olegych.scastie.web.routes
-import akka.NotUsed
+import scala.concurrent.duration.DurationInt
+import scala.concurrent.Future
+
import akka.actor.ActorRef
import akka.http.scaladsl.coding.Coders.Gzip
import akka.http.scaladsl.marshalling.sse.EventStreamMarshalling._
@@ -11,14 +13,13 @@ import akka.http.scaladsl.server.Directives._
import akka.http.scaladsl.server.Route
import akka.pattern.ask
import akka.stream.scaladsl._
+import akka.NotUsed
import com.olegych.scastie.api._
import com.olegych.scastie.balancer._
import play.api.libs.json.Json
-import scala.concurrent.Future
-import scala.concurrent.duration.DurationInt
-
class ProgressRoutes(progressActor: ActorRef) {
+
val routes: Route = encodeResponseWith(Gzip)(
concat(
snippetIdStart("progress-sse") { sid =>
@@ -28,14 +29,12 @@ class ProgressRoutes(progressActor: ActorRef) {
}
}
},
- snippetIdStart("progress-ws")(
- sid => handleWebSocketMessages(webSocket(sid))
- )
+ snippetIdStart("progress-ws")(sid => handleWebSocketMessages(webSocket(sid)))
)
)
private def progressSource(
- snippetId: SnippetId
+ snippetId: SnippetId
): Source[SnippetProgress, NotUsed] = {
Source
.fromFuture((progressActor ? SubscribeProgress(snippetId))(1.second).mapTo[Source[SnippetProgress, NotUsed]])
@@ -44,7 +43,7 @@ class ProgressRoutes(progressActor: ActorRef) {
private def webSocket(snippetId: SnippetId): Flow[ws.Message, ws.Message, _] = {
def flow: Flow[String, SnippetProgress, NotUsed] = {
- val in = Flow[String].to(Sink.ignore)
+ val in = Flow[String].to(Sink.ignore)
val out = progressSource(snippetId)
Flow.fromSinkAndSource(in, out)
}
@@ -55,8 +54,7 @@ class ProgressRoutes(progressActor: ActorRef) {
case e => Future.failed(new Exception(e.toString))
}
.via(flow)
- .map(
- progress => ws.TextMessage.Strict(Json.stringify(Json.toJson(progress)))
- )
+ .map(progress => ws.TextMessage.Strict(Json.stringify(Json.toJson(progress))))
}
+
}
diff --git a/server/src/main/scala/com.olegych.scastie.web/routes/ScalaJsRoutes.scala b/server/src/main/scala/com.olegych.scastie.web/routes/ScalaJsRoutes.scala
index b69f5da16..949ee084b 100644
--- a/server/src/main/scala/com.olegych.scastie.web/routes/ScalaJsRoutes.scala
+++ b/server/src/main/scala/com.olegych.scastie.web/routes/ScalaJsRoutes.scala
@@ -1,50 +1,47 @@
package com.olegych.scastie.web.routes
-import com.olegych.scastie.api._
-
-import akka.util.Timeout
+import scala.concurrent.duration.DurationInt
-import akka.pattern.ask
import akka.actor.{ActorRef, ActorSystem}
+import akka.http.scaladsl.coding.Coders.Gzip
import akka.http.scaladsl.server.Directives._
import akka.http.scaladsl.server.Route
-import akka.http.scaladsl.coding.Coders.Gzip
-
-import scala.concurrent.duration.DurationInt
+import akka.pattern.ask
+import akka.util.Timeout
+import com.olegych.scastie.api._
//not used anymore
-class ScalaJsRoutes(dispatchActor: ActorRef)(implicit system: ActorSystem) {
+class ScalaJsRoutes(dispatchActor: ActorRef)(
+ implicit system: ActorSystem
+) {
import system.dispatcher
implicit val timeout: Timeout = Timeout(1.seconds)
- val routes: Route =
- encodeResponseWith(Gzip)(
- concat(
- snippetIdEnd(Shared.scalaJsHttpPathPrefix, ScalaTarget.Js.targetFilename)(
- sid =>
- complete(
- (dispatchActor ? FetchScalaJs(sid))
- .mapTo[Option[FetchResultScalaJs]]
- .map(_.map(_.content))
- )
- ),
- snippetIdEnd(Shared.scalaJsHttpPathPrefix, ScalaTarget.Js.sourceFilename)(
- sid =>
- complete(
- (dispatchActor ? FetchScalaSource(sid))
- .mapTo[Option[FetchResultScalaSource]]
- .map(_.map(_.content))
- )
- ),
- snippetIdEnd(Shared.scalaJsHttpPathPrefix, ScalaTarget.Js.sourceMapFilename)(
- sid =>
- complete(
- (dispatchActor ? FetchScalaJsSourceMap(sid))
- .mapTo[Option[FetchResultScalaJsSourceMap]]
- .map(_.map(_.content))
- )
+ val routes: Route = encodeResponseWith(Gzip)(
+ concat(
+ snippetIdEnd(Shared.scalaJsHttpPathPrefix, ScalaTarget.Js.targetFilename)(sid =>
+ complete(
+ (dispatchActor ? FetchScalaJs(sid))
+ .mapTo[Option[FetchResultScalaJs]]
+ .map(_.map(_.content))
+ )
+ ),
+ snippetIdEnd(Shared.scalaJsHttpPathPrefix, ScalaTarget.Js.sourceFilename)(sid =>
+ complete(
+ (dispatchActor ? FetchScalaSource(sid))
+ .mapTo[Option[FetchResultScalaSource]]
+ .map(_.map(_.content))
+ )
+ ),
+ snippetIdEnd(Shared.scalaJsHttpPathPrefix, ScalaTarget.Js.sourceMapFilename)(sid =>
+ complete(
+ (dispatchActor ? FetchScalaJsSourceMap(sid))
+ .mapTo[Option[FetchResultScalaJsSourceMap]]
+ .map(_.map(_.content))
)
)
)
+ )
+
}
diff --git a/server/src/main/scala/com.olegych.scastie.web/routes/ScalaLangRoutes.scala b/server/src/main/scala/com.olegych.scastie.web/routes/ScalaLangRoutes.scala
index d5404b392..cc8ad3a6c 100644
--- a/server/src/main/scala/com.olegych.scastie.web/routes/ScalaLangRoutes.scala
+++ b/server/src/main/scala/com.olegych.scastie.web/routes/ScalaLangRoutes.scala
@@ -1,26 +1,24 @@
package com.olegych.scastie.web.routes
-import com.olegych.scastie.api._
-import com.olegych.scastie.web.oauth2._
-
-import com.olegych.scastie.balancer._
+import scala.concurrent.duration.DurationInt
-import akka.util.Timeout
import akka.actor.{ActorRef, ActorSystem}
-
import akka.http.scaladsl.model.StatusCodes.Created
-import akka.http.scaladsl.server.Route
import akka.http.scaladsl.server.Directives._
-
+import akka.http.scaladsl.server.Route
import akka.pattern.ask
-
-import scala.concurrent.duration.DurationInt
+import akka.util.Timeout
+import com.olegych.scastie.api._
+import com.olegych.scastie.balancer._
+import com.olegych.scastie.web.oauth2._
// temporary route for the scala-lang frontpage
class ScalaLangRoutes(
- dispatchActor: ActorRef,
- userDirectives: UserDirectives
-)(implicit system: ActorSystem) {
+ dispatchActor: ActorRef,
+ userDirectives: UserDirectives
+)(
+ implicit system: ActorSystem
+) {
import system.dispatcher
import userDirectives.optionalLogin
diff --git a/server/src/main/scala/com.olegych.scastie.web/routes/StatusRoutes.scala b/server/src/main/scala/com.olegych.scastie.web/routes/StatusRoutes.scala
index d5f18211d..0202fab53 100644
--- a/server/src/main/scala/com.olegych.scastie.web/routes/StatusRoutes.scala
+++ b/server/src/main/scala/com.olegych.scastie.web/routes/StatusRoutes.scala
@@ -1,73 +1,69 @@
package com.olegych.scastie.web.routes
-import akka.NotUsed
+import scala.concurrent.{ExecutionContext, Future}
+import scala.concurrent.duration.DurationInt
+
import akka.actor.ActorRef
import akka.http.scaladsl.marshalling.sse.EventStreamMarshalling._
import akka.http.scaladsl.model._
import akka.http.scaladsl.model.sse.ServerSentEvent
import akka.http.scaladsl.model.ws.TextMessage._
-import akka.http.scaladsl.server.Directives._
import akka.http.scaladsl.server.{Route, _}
+import akka.http.scaladsl.server.Directives._
import akka.pattern.ask
import akka.stream.scaladsl._
+import akka.NotUsed
import com.olegych.scastie.api._
import com.olegych.scastie.balancer._
import com.olegych.scastie.web.oauth2.UserDirectives
import play.api.libs.json.Json
-import scala.concurrent.duration.DurationInt
-import scala.concurrent.{ExecutionContext, Future}
+class StatusRoutes(statusActor: ActorRef, userDirectives: UserDirectives)(
+ implicit ec: ExecutionContext
+) {
-class StatusRoutes(statusActor: ActorRef, userDirectives: UserDirectives)(implicit ec: ExecutionContext) {
-
- val isAdminUser: Directive1[Boolean] =
- userDirectives.optionalLogin.map(
- user => user.exists(_.isAdmin)
- )
+ val isAdminUser: Directive1[Boolean] = userDirectives.optionalLogin.map(user => user.exists(_.isAdmin))
- val routes: Route =
- isAdminUser { isAdmin =>
- concat(
- path("status-sse")(
- complete(
- statusSource(isAdmin).map { progress =>
- ServerSentEvent(
- Json.stringify(Json.toJson(progress))
- )
- }
- )
- ),
- path("status-ws")(
- handleWebSocketMessages(webSocketProgress(isAdmin))
+ val routes: Route = isAdminUser { isAdmin =>
+ concat(
+ path("status-sse")(
+ complete(
+ statusSource(isAdmin).map { progress =>
+ ServerSentEvent(
+ Json.stringify(Json.toJson(progress))
+ )
+ }
)
+ ),
+ path("status-ws")(
+ handleWebSocketMessages(webSocketProgress(isAdmin))
)
- }
+ )
+ }
private def statusSource(isAdmin: Boolean) = {
def hideTask(progress: StatusProgress): StatusProgress =
if (isAdmin) progress
- else
- progress match {
- case StatusProgress.Sbt(runners) =>
- // Hide the task Queue for non admin users,
- // they will only see the runner count
- StatusProgress.Sbt(
- runners.map(_.copy(tasks = Vector()))
- )
+ else progress match {
+ case StatusProgress.Sbt(runners) =>
+ // Hide the task Queue for non admin users,
+ // they will only see the runner count
+ StatusProgress.Sbt(
+ runners.map(_.copy(tasks = Vector()))
+ )
- case _ =>
- progress
- }
+ case _ => progress
+ }
Source
.fromFuture((statusActor ? SubscribeStatus)(2.seconds).mapTo[Source[StatusProgress, NotUsed]])
.flatMapConcat(s => s.map(hideTask))
}
private def webSocketProgress(
- isAdmin: Boolean
+ isAdmin: Boolean
): Flow[ws.Message, ws.Message, _] = {
def flow: Flow[String, StatusProgress, NotUsed] = {
- val in = Flow[String].to(Sink.ignore)
+ val in = Flow[String].to(Sink.ignore)
val out = statusSource(isAdmin)
Flow.fromSinkAndSource(in, out)
}
@@ -78,8 +74,7 @@ class StatusRoutes(statusActor: ActorRef, userDirectives: UserDirectives)(implic
case e => Future.failed(new Exception(e.toString))
}
.via(flow)
- .map(
- progress => ws.TextMessage.Strict(Json.stringify(Json.toJson(progress)))
- )
+ .map(progress => ws.TextMessage.Strict(Json.stringify(Json.toJson(progress))))
}
+
}
diff --git a/server/src/main/scala/com.olegych.scastie.web/routes/package.scala b/server/src/main/scala/com.olegych.scastie.web/routes/package.scala
index d1885ae8b..595e70a0f 100644
--- a/server/src/main/scala/com.olegych.scastie.web/routes/package.scala
+++ b/server/src/main/scala/com.olegych.scastie.web/routes/package.scala
@@ -1,49 +1,46 @@
package com.olegych.scastie.web
-import com.olegych.scastie.api._
-
-import akka.http.scaladsl.server.Directives._
import akka.http.scaladsl.server.{PathMatcher, Route}
+import akka.http.scaladsl.server.Directives._
+import com.olegych.scastie.api._
package object routes {
- def snippetIdStart(matcherStart: String)(f: SnippetId => Route): Route =
- snippetIdBase(
- matcherStart / _,
- matcherStart / _
- )(f)
-
- def snippetId(f: SnippetId => Route): Route =
- snippetIdBase(
- p => p,
- p => p
- )(f)
-
- def snippetIdEnd(matcherStart: String, matcherEnd: String)(f: SnippetId => Route): Route =
- snippetIdBase(
- matcherStart / _ / matcherEnd,
- matcherStart / _ / matcherEnd
- )(f)
-
- def snippetIdExtension(extension: String)(f: SnippetId => Route): Route =
- snippetIdBase(
- _ ~ extension,
- _ ~ extension
- )(f)
+
+ def snippetIdStart(matcherStart: String)(f: SnippetId => Route): Route = snippetIdBase(
+ matcherStart / _,
+ matcherStart / _
+ )(f)
+
+ def snippetId(f: SnippetId => Route): Route = snippetIdBase(
+ p => p,
+ p => p
+ )(f)
+
+ def snippetIdEnd(matcherStart: String, matcherEnd: String)(f: SnippetId => Route): Route = snippetIdBase(
+ matcherStart / _ / matcherEnd,
+ matcherStart / _ / matcherEnd
+ )(f)
+
+ def snippetIdExtension(extension: String)(f: SnippetId => Route): Route = snippetIdBase(
+ _ ~ extension,
+ _ ~ extension
+ )(f)
private val uuidMatcher = PathMatcher("[A-Za-z0-9]{22}".r)
private def snippetIdBase(
- fp1: PathMatcher[Tuple1[String]] => PathMatcher[Tuple1[String]],
- fp2: PathMatcher[(String, String, Option[Int])] => PathMatcher[
- (String, String, Option[Int])
- ]
+ fp1: PathMatcher[Tuple1[String]] => PathMatcher[Tuple1[String]],
+ fp2: PathMatcher[(String, String, Option[Int])] => PathMatcher[
+ (String, String, Option[Int])
+ ]
)(f: SnippetId => Route): Route = {
concat(
path(fp1(uuidMatcher) ~ Slash.?)(uuid => f(SnippetId(uuid, None))),
- path(fp2(Segment / uuidMatcher ~ (Slash ~ IntNumber).?) ~ Slash.?)(
- (user, uuid, update) => f(SnippetId(uuid, Some(SnippetUserPart(user, update.getOrElse(0)))))
+ path(fp2(Segment / uuidMatcher ~ (Slash ~ IntNumber).?) ~ Slash.?)((user, uuid, update) =>
+ f(SnippetId(uuid, Some(SnippetUserPart(user, update.getOrElse(0)))))
)
)
}
+
}
diff --git a/server/src/test/scala/com.olegych.scastie.web.routes/SnippetIdMatcherTests.scala b/server/src/test/scala/com.olegych.scastie.web.routes/SnippetIdMatcherTests.scala
index b68c4264a..d9c248cac 100644
--- a/server/src/test/scala/com.olegych.scastie.web.routes/SnippetIdMatcherTests.scala
+++ b/server/src/test/scala/com.olegych.scastie.web.routes/SnippetIdMatcherTests.scala
@@ -12,11 +12,10 @@ class SnippetIdMatcherTests extends AnyFunSuite with ScalatestRouteTest {
def testRoute(snippetIdRoute: Route, f1: String => String, f2: String => String, checkEnd: Boolean = true): Unit = {
- val expectedBase =
- SnippetId(
- "GIbgJuUFSKaVzLDGK4kxdw",
- None
- )
+ val expectedBase = SnippetId(
+ "GIbgJuUFSKaVzLDGK4kxdw",
+ None
+ )
println(f1("/GIbgJuUFSKaVzLDGK4kxdw"))
Get(f1("/GIbgJuUFSKaVzLDGK4kxdw")) ~> snippetIdRoute ~> check {
@@ -31,11 +30,10 @@ class SnippetIdMatcherTests extends AnyFunSuite with ScalatestRouteTest {
}
}
- val expectedUser =
- SnippetId(
- "GIbgJuUFSKaVzLDGK4kxdw",
- Some(SnippetUserPart("MasseGuillaume", 0))
- )
+ val expectedUser = SnippetId(
+ "GIbgJuUFSKaVzLDGK4kxdw",
+ Some(SnippetUserPart("MasseGuillaume", 0))
+ )
Get(f1("/MasseGuillaume/GIbgJuUFSKaVzLDGK4kxdw")) ~> snippetIdRoute ~> check {
val obtained = responseAs[SnippetId]
@@ -49,11 +47,10 @@ class SnippetIdMatcherTests extends AnyFunSuite with ScalatestRouteTest {
}
}
- val expectedFull =
- SnippetId(
- "GIbgJuUFSKaVzLDGK4kxdw",
- Some(SnippetUserPart("MasseGuillaume", 2))
- )
+ val expectedFull = SnippetId(
+ "GIbgJuUFSKaVzLDGK4kxdw",
+ Some(SnippetUserPart("MasseGuillaume", 2))
+ )
Get(f1("/MasseGuillaume/GIbgJuUFSKaVzLDGK4kxdw/2")) ~> snippetIdRoute ~> check {
val obtained = responseAs[SnippetId]
@@ -69,10 +66,9 @@ class SnippetIdMatcherTests extends AnyFunSuite with ScalatestRouteTest {
}
test("snippetId") {
- val snippetIdRoute =
- get(
- snippetId(sid => complete(sid))
- )
+ val snippetIdRoute = get(
+ snippetId(sid => complete(sid))
+ )
testRoute(
snippetIdRoute,
@@ -84,10 +80,9 @@ class SnippetIdMatcherTests extends AnyFunSuite with ScalatestRouteTest {
test("snippetIdStart") {
val start = "snippets"
- val snippetIdRoute =
- get(
- snippetIdStart(start)(sid => complete(sid))
- )
+ val snippetIdRoute = get(
+ snippetIdStart(start)(sid => complete(sid))
+ )
testRoute(
snippetIdRoute,
@@ -98,12 +93,11 @@ class SnippetIdMatcherTests extends AnyFunSuite with ScalatestRouteTest {
test("snippetIdEnd") {
val start = "api"
- val end = "foo"
+ val end = "foo"
- val snippetIdRoute =
- get(
- snippetIdEnd(start, end)(sid => complete(sid))
- )
+ val snippetIdRoute = get(
+ snippetIdEnd(start, end)(sid => complete(sid))
+ )
testRoute(
snippetIdRoute,
@@ -115,10 +109,9 @@ class SnippetIdMatcherTests extends AnyFunSuite with ScalatestRouteTest {
test("snippetIdExtension") {
val extension = ".js"
- val snippetIdRoute =
- get(
- snippetIdExtension(extension)(sid => complete(sid))
- )
+ val snippetIdRoute = get(
+ snippetIdExtension(extension)(sid => complete(sid))
+ )
testRoute(
snippetIdRoute,
diff --git a/storage/src/main/scala/com.olegych.scastie.storage/OldScastieConverter.scala b/storage/src/main/scala/com.olegych.scastie.storage/OldScastieConverter.scala
index 0d99384b1..07ad40914 100644
--- a/storage/src/main/scala/com.olegych.scastie.storage/OldScastieConverter.scala
+++ b/storage/src/main/scala/com.olegych.scastie.storage/OldScastieConverter.scala
@@ -3,6 +3,7 @@ package com.olegych.scastie.storage
import com.olegych.scastie.api._
object OldScastieConverter {
+
private def convertLine(line: String): Converter => Converter = { converter =>
val sv = "scalaVersion := \""
@@ -16,11 +17,9 @@ object OldScastieConverter {
case """scalaOrganization in ThisBuild := "org.typelevel"""" =>
converter.setTargetType(ScalaTargetType.Typelevel)
- case "coursier.CoursierPlugin.projectSettings" =>
- converter
+ case "coursier.CoursierPlugin.projectSettings" => converter
- case _ =>
- converter.appendSbt(line)
+ case _ => converter.appendSbt(line)
}
}
}
@@ -28,11 +27,10 @@ object OldScastieConverter {
def convertOldOutput(content: String): List[SnippetProgress] = {
content
.split("\n")
- .map(
- line =>
- SnippetProgress.default.copy(
- userOutput = Some(ProcessOutput(line, ProcessOutputType.StdOut, None)),
- isDone = true
+ .map(line =>
+ SnippetProgress.default.copy(
+ userOutput = Some(ProcessOutput(line, ProcessOutputType.StdOut, None)),
+ isDone = true
)
)
.toList
@@ -40,22 +38,20 @@ object OldScastieConverter {
def convertOldInput(content: String): Inputs = {
val blockStart = "/***"
- val blockEnd = "*/"
+ val blockEnd = "*/"
val blockStartPos = content.indexOf(blockStart)
- val blockEndPos = content.indexOf(blockEnd)
+ val blockEndPos = content.indexOf(blockEnd)
if (blockStartPos != -1 && blockEndPos != -1 && blockEndPos > blockStartPos) {
val start = blockStartPos + blockStart.length
val sbtConfig = content.slice(start, start + blockEndPos - start)
- val code = content.drop(blockEndPos + blockEnd.length)
+ val code = content.drop(blockEndPos + blockEnd.length)
- val converterFn =
- sbtConfig.split("\n").foldLeft(Converter.nil) {
- case (converter, line) =>
- convertLine(line)(converter)
- }
+ val converterFn = sbtConfig.split("\n").foldLeft(Converter.nil) { case (converter, line) =>
+ convertLine(line)(converter)
+ }
converterFn(Inputs.default).copy(code = code.trim)
} else {
@@ -64,45 +60,40 @@ object OldScastieConverter {
}
private object Converter {
- def nil: Converter =
- Converter(
- scalaVersion = None,
- targetType = None,
- sbtExtra = ""
- )
+
+ def nil: Converter = Converter(
+ scalaVersion = None,
+ targetType = None,
+ sbtExtra = ""
+ )
+
}
private case class Converter(
- scalaVersion: Option[String],
- targetType: Option[ScalaTargetType],
- sbtExtra: String
+ scalaVersion: Option[String],
+ targetType: Option[ScalaTargetType],
+ sbtExtra: String
) {
- def appendSbt(in: String): Converter =
- copy(sbtExtra = sbtExtra + "\n" + in)
+ def appendSbt(in: String): Converter = copy(sbtExtra = sbtExtra + "\n" + in)
- def setTargetType(targetType0: ScalaTargetType): Converter =
- copy(targetType = Some(targetType0))
+ def setTargetType(targetType0: ScalaTargetType): Converter = copy(targetType = Some(targetType0))
def apply(inputs: Inputs): Inputs = {
- val scalaTarget =
- targetType match {
- case Some(ScalaTargetType.Scala3) =>
- ScalaTarget.Scala3.default
-
- case Some(ScalaTargetType.Typelevel) =>
- scalaVersion
- .map(sv => ScalaTarget.Typelevel(sv))
- .getOrElse(
- ScalaTarget.Typelevel.default
- )
-
- case _ =>
- scalaVersion
- .map(sv => ScalaTarget.Jvm(sv))
- .getOrElse(
- ScalaTarget.Jvm.default
- )
- }
+ val scalaTarget = targetType match {
+ case Some(ScalaTargetType.Scala3) => ScalaTarget.Scala3.default
+
+ case Some(ScalaTargetType.Typelevel) => scalaVersion
+ .map(sv => ScalaTarget.Typelevel(sv))
+ .getOrElse(
+ ScalaTarget.Typelevel.default
+ )
+
+ case _ => scalaVersion
+ .map(sv => ScalaTarget.Jvm(sv))
+ .getOrElse(
+ ScalaTarget.Jvm.default
+ )
+ }
inputs.copy(
target = scalaTarget,
@@ -110,5 +101,7 @@ object OldScastieConverter {
_isWorksheetMode = false
)
}
+
}
+
}
diff --git a/storage/src/main/scala/com.olegych.scastie.storage/SnippetsContainer.scala b/storage/src/main/scala/com.olegych.scastie.storage/SnippetsContainer.scala
index 74a374172..66c552928 100644
--- a/storage/src/main/scala/com.olegych.scastie.storage/SnippetsContainer.scala
+++ b/storage/src/main/scala/com.olegych.scastie.storage/SnippetsContainer.scala
@@ -1,29 +1,27 @@
package com.olegych.scastie.storage
+import java.nio.file.{Files, Path, Paths}
+import scala.concurrent.{ExecutionContext, Future}
+
import com.olegych.scastie.api._
import com.olegych.scastie.instrumentation.Instrument
import com.olegych.scastie.util.Base64UUID
-
-import net.lingala.zip4j.ZipFile
import net.lingala.zip4j.model.ZipParameters
-
-import java.nio.file.{Files, Path, Paths}
-import scala.concurrent.{ExecutionContext, Future}
-
+import net.lingala.zip4j.ZipFile
trait SnippetsContainer {
protected implicit val ec: ExecutionContext
def appendOutput(progress: SnippetProgress): Future[Unit]
+
def deleteAll(snippetId: SnippetId): Future[Boolean] = {
def deleteUpdate(update: Int): Future[Boolean] = {
val updateSnippetId = snippetId.copy(user = snippetId.user.map(_.copy(update = update)))
for {
read <- readSnippet(updateSnippetId)
result <- read match {
- case Some(read) =>
- for {
- result <- delete(updateSnippetId)
+ case Some(read) => for {
+ result <- delete(updateSnippetId)
resultNext <- deleteUpdate(update + 1)
} yield result || resultNext
case None => Future.successful(false)
@@ -32,22 +30,28 @@ trait SnippetsContainer {
}
deleteUpdate(0)
}
+
protected def delete(snippetId: SnippetId): Future[Boolean]
+
def removeUserSnippets(user: UserLogin): Future[Boolean] = {
listSnippets(user).flatMap(snippets => {
- Future.sequence(
- snippets
- .map(snippet => deleteAll(snippet.snippetId)))
- .map(_.fold(true)(_ && _)
- )
+ Future
+ .sequence(
+ snippets
+ .map(snippet => deleteAll(snippet.snippetId))
+ )
+ .map(_.fold(true)(_ && _))
})
}
+
def listSnippets(user: UserLogin): Future[List[SnippetSummary]]
def readOldSnippet(id: Int): Future[Option[FetchResult]]
def readScalaJs(snippetId: SnippetId): Future[Option[FetchResultScalaJs]]
+
def readScalaJsSourceMap(
- snippetId: SnippetId
+ snippetId: SnippetId
): Future[Option[FetchResultScalaJsSourceMap]]
+
def readSnippet(snippetId: SnippetId): Future[Option[FetchResult]]
protected def insert(snippetId: SnippetId, inputs: Inputs): Future[Unit]
protected def hideFromUserProfile(snippetId: SnippetId): Future[Unit]
@@ -64,8 +68,7 @@ trait SnippetsContainer {
final def update(snippetId: SnippetId, inputs: Inputs): Future[Option[SnippetId]] = {
updateSnippetId(snippetId).flatMap {
- case Some(nextSnippetId) =>
- for {
+ case Some(nextSnippetId) => for {
r <- insert0(nextSnippetId, inputs.copy(forked = Some(snippetId), isShowingInUserProfile = true))
_ <- hideFromUserProfile(snippetId)
} yield Some(r)
@@ -77,18 +80,15 @@ trait SnippetsContainer {
create(inputs.copy(forked = Some(snippetId), isShowingInUserProfile = true), user)
final def readScalaSource(
- snippetId: SnippetId
- ): Future[Option[FetchResultScalaSource]] =
- readSnippet(snippetId).map(
- _.flatMap(
- snippet =>
- Instrument(snippet.inputs.code, snippet.inputs.target) match {
- case Right(instrumented) =>
- Some(FetchResultScalaSource(instrumented))
- case _ => None
- }
- )
+ snippetId: SnippetId
+ ): Future[Option[FetchResultScalaSource]] = readSnippet(snippetId).map(
+ _.flatMap(snippet =>
+ Instrument(snippet.inputs.code, snippet.inputs.target) match {
+ case Right(instrumented) => Some(FetchResultScalaSource(instrumented))
+ case _ => None
+ }
)
+ )
final def downloadSnippet(snippetId: SnippetId): Future[Option[Path]] =
readSnippet(snippetId).map(_.map(asZip(snippetId)))
@@ -129,11 +129,17 @@ trait SnippetsContainer {
Files.createDirectories(projectDir)
val buildFile = projectDir.resolve("build.sbt")
- Files.write(buildFile, inputs.sbtConfig.linesIterator.filterNot(_.contains("org.scastie")).mkString("\n").getBytes())
+ Files.write(
+ buildFile,
+ inputs.sbtConfig.linesIterator.filterNot(_.contains("org.scastie")).mkString("\n").getBytes()
+ )
val projectFile = projectDir.resolve("project/plugins.sbt")
Files.createDirectories(projectFile.getParent)
- Files.write(projectFile, inputs.sbtPluginsConfig.linesIterator.filterNot(_.contains("org.scastie")).mkString("\n").getBytes())
+ Files.write(
+ projectFile,
+ inputs.sbtPluginsConfig.linesIterator.filterNot(_.contains("org.scastie")).mkString("\n").getBytes()
+ )
val codeFile = projectDir.resolve(s"src/main/scala/main.${if (inputs.isWorksheetMode) "sc" else "scala"}")
Files.createDirectories(codeFile.getParent)
diff --git a/storage/src/main/scala/com.olegych.scastie.storage/filesystem/FilesystemContainer.scala b/storage/src/main/scala/com.olegych.scastie.storage/filesystem/FilesystemContainer.scala
index a21e8d224..2d03809f0 100644
--- a/storage/src/main/scala/com.olegych.scastie.storage/filesystem/FilesystemContainer.scala
+++ b/storage/src/main/scala/com.olegych.scastie.storage/filesystem/FilesystemContainer.scala
@@ -5,4 +5,5 @@ import scala.concurrent.ExecutionContext
class FilesystemContainer(val root: Path, val oldRoot: Path)(
implicit val ec: ExecutionContext
-) extends FilesystemUsersContainer with FilesystemSnippetsContainer
+) extends FilesystemUsersContainer
+ with FilesystemSnippetsContainer
diff --git a/storage/src/main/scala/com.olegych.scastie.storage/filesystem/FilesystemSnippetsContainer.scala b/storage/src/main/scala/com.olegych.scastie.storage/filesystem/FilesystemSnippetsContainer.scala
index 4b1965df7..81820b8b9 100644
--- a/storage/src/main/scala/com.olegych.scastie.storage/filesystem/FilesystemSnippetsContainer.scala
+++ b/storage/src/main/scala/com.olegych.scastie.storage/filesystem/FilesystemSnippetsContainer.scala
@@ -1,18 +1,16 @@
package com.olegych.scastie.storage.filesystem
+import java.io.IOException
+import java.nio.file._
+import scala.concurrent.Future
+
import com.olegych.scastie.api._
import com.olegych.scastie.storage.OldScastieConverter
import com.olegych.scastie.storage.SnippetsContainer
import com.olegych.scastie.storage.UserLogin
import play.api.libs.json.Json
-
-import java.io.IOException
-import java.nio.file._
-import scala.concurrent.Future
-
import System.{lineSeparator => nl}
-
trait FilesystemSnippetsContainer extends SnippetsContainer with GenericFilesystemContainer {
val root: Path
val oldRoot: Path
@@ -25,17 +23,14 @@ trait FilesystemSnippetsContainer extends SnippetsContainer with GenericFilesyst
case _ => ()
}
- progress.snippetId.foreach(
- sid => append(outputsFile(sid), Json.stringify(Json.toJson(progress)) + nl)
- )
+ progress.snippetId.foreach(sid => append(outputsFile(sid), Json.stringify(Json.toJson(progress)) + nl))
}
def delete(snippetId: SnippetId): Future[Boolean] = {
def rootDir(snippetId: SnippetId): Path = {
snippetId.user match {
- case Some(SnippetUserPart(login, _)) =>
- root.resolve(login)
- case _ => root.resolve(anonFolder)
+ case Some(SnippetUserPart(login, _)) => root.resolve(login)
+ case _ => root.resolve(anonFolder)
}
}
@@ -79,7 +74,7 @@ trait FilesystemSnippetsContainer extends SnippetsContainer with GenericFilesyst
import java.nio.file.attribute.BasicFileAttributes
val filePath = inputsFile(snippetId)
- val attr = Files.readAttributes(filePath, classOf[BasicFileAttributes])
+ val attr = Files.readAttributes(filePath, classOf[BasicFileAttributes])
attr.creationTime().toMillis
}
@@ -104,8 +99,7 @@ trait FilesystemSnippetsContainer extends SnippetsContainer with GenericFilesyst
updates
.flatMap { update =>
- val snippetId =
- SnippetId(uuid, Some(SnippetUserPart(user.login, update)))
+ val snippetId = SnippetId(uuid, Some(SnippetUserPart(user.login, update)))
readInputs(snippetId) match {
case Some(inputs) =>
if (inputs.isShowingInUserProfile) {
@@ -129,10 +123,9 @@ trait FilesystemSnippetsContainer extends SnippetsContainer with GenericFilesyst
def readOldSnippet(id: Int): Future[Option[FetchResult]] = {
- def oldPath(id: Int): Path =
- oldRoot
- .resolve("paste%20d".format(id).replaceAll(" ", "0"))
- .resolve("src/main/scala/")
+ def oldPath(id: Int): Path = oldRoot
+ .resolve("paste%20d".format(id).replaceAll(" ", "0"))
+ .resolve("src/main/scala/")
def readOldInputs(id: Int): Option[Inputs] = {
slurp(oldPath(id).resolve("test.scala"))
@@ -145,47 +138,40 @@ trait FilesystemSnippetsContainer extends SnippetsContainer with GenericFilesyst
}
Future {
- readOldInputs(id).map(
- inputs => FetchResult.create(inputs, readOldOutputs(id).getOrElse(Nil))
- )
+ readOldInputs(id).map(inputs => FetchResult.create(inputs, readOldOutputs(id).getOrElse(Nil)))
}
}
- def readScalaJs(snippetId: SnippetId): Future[Option[FetchResultScalaJs]] =
- Future {
- slurp(scalaJsFile(snippetId)).map(content => FetchResultScalaJs(content))
- }
+ def readScalaJs(snippetId: SnippetId): Future[Option[FetchResultScalaJs]] = Future {
+ slurp(scalaJsFile(snippetId)).map(content => FetchResultScalaJs(content))
+ }
def readScalaJsSourceMap(
- snippetId: SnippetId
+ snippetId: SnippetId
): Future[Option[FetchResultScalaJsSourceMap]] = Future {
slurp(scalaJsSourceMapFile(snippetId))
.map(content => FetchResultScalaJsSourceMap(content))
}
def readSnippet(snippetId: SnippetId): Future[Option[FetchResult]] = Future {
- readInputs(snippetId).map(
- inputs => FetchResult.create(inputs, readOutputs(snippetId).getOrElse(Nil))
- )
+ readInputs(snippetId).map(inputs => FetchResult.create(inputs, readOutputs(snippetId).getOrElse(Nil)))
}
- protected def insert(snippetId: SnippetId, inputs: Inputs): Future[Unit] =
- Future {
- write(inputsFile(snippetId), Json.prettyPrint(Json.toJson(inputs.withSavedConfig)))
- }
+ protected def insert(snippetId: SnippetId, inputs: Inputs): Future[Unit] = Future {
+ write(inputsFile(snippetId), Json.prettyPrint(Json.toJson(inputs.withSavedConfig)))
+ }
- override protected def hideFromUserProfile(snippetId: SnippetId): Future[Unit] =
- for {
- old <- readSnippet(snippetId)
- _ <- Future.traverse(old.toList) { old =>
- insert(snippetId, old.inputs.copy(isShowingInUserProfile = false))
- }
- } yield ()
+ override protected def hideFromUserProfile(snippetId: SnippetId): Future[Unit] = for {
+ old <- readSnippet(snippetId)
+ _ <- Future.traverse(old.toList) { old =>
+ insert(snippetId, old.inputs.copy(isShowingInUserProfile = false))
+ }
+ } yield ()
- private val anonFolder = "_anonymous_"
- private val inputFileName = "input3.json"
- private val outputFileName = "output3.json"
- private val scalaJsFileName = ScalaTarget.Js.targetFilename
+ private val anonFolder = "_anonymous_"
+ private val inputFileName = "input3.json"
+ private val outputFileName = "output3.json"
+ private val scalaJsFileName = ScalaTarget.Js.targetFilename
private val scalaJsSourceMapFileName = ScalaTarget.Js.sourceMapFilename
private def inputsFile(snippetId: SnippetId): Path = {
@@ -207,43 +193,41 @@ trait FilesystemSnippetsContainer extends SnippetsContainer with GenericFilesyst
private def snippetFile(snippetId: SnippetId, fileName: String): Path = {
if (!Files.exists(root)) Files.createDirectory(root)
- val baseDirectory =
- snippetId.user match {
- case Some(SnippetUserPart(login, update)) =>
- val userFolder = root.resolve(login)
- if (!Files.exists(userFolder)) Files.createDirectory(userFolder)
+ val baseDirectory = snippetId.user match {
+ case Some(SnippetUserPart(login, update)) =>
+ val userFolder = root.resolve(login)
+ if (!Files.exists(userFolder)) Files.createDirectory(userFolder)
- val base = userFolder.resolve(snippetId.base64UUID)
- if (!Files.exists(base)) Files.createDirectory(base)
+ val base = userFolder.resolve(snippetId.base64UUID)
+ if (!Files.exists(base)) Files.createDirectory(base)
- val baseVersion = base.resolve(update.toString)
- if (!Files.exists(baseVersion)) Files.createDirectory(baseVersion)
+ val baseVersion = base.resolve(update.toString)
+ if (!Files.exists(baseVersion)) Files.createDirectory(baseVersion)
- baseVersion
- case None =>
- val anon = root.resolve(anonFolder)
- if (!Files.exists(anon)) Files.createDirectory(anon)
+ baseVersion
+ case None =>
+ val anon = root.resolve(anonFolder)
+ if (!Files.exists(anon)) Files.createDirectory(anon)
- val base = anon.resolve(snippetId.base64UUID)
- if (!Files.exists(base)) Files.createDirectory(base)
- base
- }
+ val base = anon.resolve(snippetId.base64UUID)
+ if (!Files.exists(base)) Files.createDirectory(base)
+ base
+ }
baseDirectory.resolve(Paths.get(fileName))
}
private def readInputs(snippetId: SnippetId): Option[Inputs] = {
slurp(inputsFile(snippetId))
- .map(
- content =>
- Json
- .fromJson[Inputs](Json.parse(content))
- .fold(e => sys.error(e.toString + s" for ${snippetId} $content"), identity)
+ .map(content =>
+ Json
+ .fromJson[Inputs](Json.parse(content))
+ .fold(e => sys.error(e.toString + s" for ${snippetId} $content"), identity)
)
}
private def readOutputs(
- snippetId: SnippetId
+ snippetId: SnippetId
): Option[List[SnippetProgress]] = {
slurp(outputsFile(snippetId)).map {
_.linesIterator
@@ -257,10 +241,9 @@ trait FilesystemSnippetsContainer extends SnippetsContainer with GenericFilesyst
}
}
-
private def deleteEmptyDirectories(base: Path): Unit = {
def dirIsEmpty(dir: Path): Boolean = {
- val ds = Files.newDirectoryStream(dir)
+ val ds = Files.newDirectoryStream(dir)
val ret = ds.iterator().hasNext
ds.close()
!ret
@@ -280,4 +263,5 @@ trait FilesystemSnippetsContainer extends SnippetsContainer with GenericFilesyst
()
}
+
}
diff --git a/storage/src/main/scala/com.olegych.scastie.storage/filesystem/FilesystemUsersContainer.scala b/storage/src/main/scala/com.olegych.scastie.storage/filesystem/FilesystemUsersContainer.scala
index f8c999665..d0174a6f9 100644
--- a/storage/src/main/scala/com.olegych.scastie.storage/filesystem/FilesystemUsersContainer.scala
+++ b/storage/src/main/scala/com.olegych.scastie.storage/filesystem/FilesystemUsersContainer.scala
@@ -1,20 +1,21 @@
package com.olegych.scastie.storage.filesystem
+import java.nio.file._
+import scala.concurrent.Future
+import scala.util.Try
+
import com.olegych.scastie.storage.PolicyAcceptance
import com.olegych.scastie.storage.UserLogin
import com.olegych.scastie.storage.UsersContainer
import play.api.libs.json.Json
-import java.nio.file._
-import scala.concurrent.Future
-import scala.util.Try
-
trait FilesystemUsersContainer extends UsersContainer with GenericFilesystemContainer {
val root: Path
def addNewUser(user: UserLogin): Future[Boolean] = setPrivacyPolicyResponse(user, true)
+
def deleteUser(user: UserLogin): Future[Boolean] = Future {
- val userDir = root.resolve(user.login)
+ val userDir = root.resolve(user.login)
val privacyPolicyFile = userDir.resolve("policy-acceptance.json")
Try {
@@ -23,7 +24,7 @@ trait FilesystemUsersContainer extends UsersContainer with GenericFilesystemCont
}
def setPrivacyPolicyResponse(user: UserLogin, status: Boolean): Future[Boolean] = Future {
- val userDir = root.resolve(user.login)
+ val userDir = root.resolve(user.login)
val privacyPolicyFile = userDir.resolve("policy-acceptance.json")
Try {
@@ -33,18 +34,19 @@ trait FilesystemUsersContainer extends UsersContainer with GenericFilesystemCont
}.isSuccess
}
-
def getPrivacyPolicyResponse(user: UserLogin): Future[Boolean] = Future {
- val userDir = root.resolve(user.login)
+ val userDir = root.resolve(user.login)
val privacyPolicyFile = userDir.resolve("policy-acceptance.json")
- val maybePrivacyPolicy = if (Files.exists(privacyPolicyFile)) {
- val response = new String(Files.readAllBytes(privacyPolicyFile))
- Json.parse(response).asOpt[PolicyAcceptance]
- } else {
- None
- }
+ val maybePrivacyPolicy =
+ if (Files.exists(privacyPolicyFile)) {
+ val response = new String(Files.readAllBytes(privacyPolicyFile))
+ Json.parse(response).asOpt[PolicyAcceptance]
+ } else {
+ None
+ }
maybePrivacyPolicy.map(_.acceptedPrivacyPolicy).getOrElse(true)
}
+
}
diff --git a/storage/src/main/scala/com.olegych.scastie.storage/filesystem/GenericFilesystemContainer.scala b/storage/src/main/scala/com.olegych.scastie.storage/filesystem/GenericFilesystemContainer.scala
index 6f19c9aff..ab9d5a96b 100644
--- a/storage/src/main/scala/com.olegych.scastie.storage/filesystem/GenericFilesystemContainer.scala
+++ b/storage/src/main/scala/com.olegych.scastie.storage/filesystem/GenericFilesystemContainer.scala
@@ -16,4 +16,5 @@ trait GenericFilesystemContainer {
if (Files.exists(src)) Some(new String(Files.readAllBytes(src)))
else None
}
+
}
diff --git a/storage/src/main/scala/com.olegych.scastie.storage/inmemory/InMemoryContainer.scala b/storage/src/main/scala/com.olegych.scastie.storage/inmemory/InMemoryContainer.scala
index 6b9acbfa4..b7efd66ef 100644
--- a/storage/src/main/scala/com.olegych.scastie.storage/inmemory/InMemoryContainer.scala
+++ b/storage/src/main/scala/com.olegych.scastie.storage/inmemory/InMemoryContainer.scala
@@ -2,6 +2,7 @@ package com.olegych.scastie.storage.inmemory
import scala.concurrent.ExecutionContext
-class InMemoryContainer(implicit val ec: ExecutionContext) extends InMemoryUsersContainer with InMemorySnippetsContainer {
-
-}
+class InMemoryContainer(
+ implicit val ec: ExecutionContext
+) extends InMemoryUsersContainer
+ with InMemorySnippetsContainer {}
diff --git a/storage/src/main/scala/com.olegych.scastie.storage/inmemory/InMemorySnippetsContainer.scala b/storage/src/main/scala/com.olegych.scastie.storage/inmemory/InMemorySnippetsContainer.scala
index 2e295a0a1..87532e4d7 100644
--- a/storage/src/main/scala/com.olegych.scastie.storage/inmemory/InMemorySnippetsContainer.scala
+++ b/storage/src/main/scala/com.olegych.scastie.storage/inmemory/InMemorySnippetsContainer.scala
@@ -1,33 +1,30 @@
package com.olegych.scastie.storage.inmemory
-import com.olegych.scastie.api._
-import com.olegych.scastie.storage.SnippetsContainer
-import com.olegych.scastie.storage.UserLogin
-
import scala.collection.mutable
import scala.concurrent.Future
+import com.olegych.scastie.api._
+import com.olegych.scastie.storage.SnippetsContainer
+import com.olegych.scastie.storage.UserLogin
import System.{lineSeparator => nl}
-
trait InMemorySnippetsContainer extends SnippetsContainer {
private val snippets = mutable.Map[SnippetId, Storage]()
case class Storage(
- snippetId: SnippetId,
- inputs: Inputs,
- progresses: mutable.Queue[SnippetProgress] = mutable.Queue(),
- var scalaJsContent: String = "",
- var scalaJsSourceMapContent: String = "",
- time: Long = System.currentTimeMillis
+ snippetId: SnippetId,
+ inputs: Inputs,
+ progresses: mutable.Queue[SnippetProgress] = mutable.Queue(),
+ var scalaJsContent: String = "",
+ var scalaJsSourceMapContent: String = "",
+ time: Long = System.currentTimeMillis
)
def appendOutput(progress: SnippetProgress): Future[Unit] = Future {
- progress.snippetId.foreach(
- id => snippets.get(id).foreach(storage => storage.progresses += progress)
- )
+ progress.snippetId.foreach(id => snippets.get(id).foreach(storage => storage.progresses += progress))
}
+
def delete(snippetId: SnippetId): Future[Boolean] = Future {
val found = snippets.contains(snippetId)
snippets -= snippetId
@@ -48,13 +45,12 @@ trait InMemorySnippetsContainer extends SnippetsContainer {
.toList
}
- def readScalaJs(snippetId: SnippetId): Future[Option[FetchResultScalaJs]] =
- Future {
- snippets.get(snippetId).map(m => FetchResultScalaJs(m.scalaJsContent))
- }
+ def readScalaJs(snippetId: SnippetId): Future[Option[FetchResultScalaJs]] = Future {
+ snippets.get(snippetId).map(m => FetchResultScalaJs(m.scalaJsContent))
+ }
def readScalaJsSourceMap(
- snippetId: SnippetId
+ snippetId: SnippetId
): Future[Option[FetchResultScalaJsSourceMap]] = Future {
snippets
.get(snippetId)
@@ -67,14 +63,14 @@ trait InMemorySnippetsContainer extends SnippetsContainer {
def readOldSnippet(id: Int): Future[Option[FetchResult]] = Future(None)
- protected def insert(snippetId: SnippetId, inputs: Inputs): Future[Unit] =
- Future {
- snippets.update(snippetId, Storage(snippetId, inputs.withSavedConfig))
- }
+ protected def insert(snippetId: SnippetId, inputs: Inputs): Future[Unit] = Future {
+ snippets.update(snippetId, Storage(snippetId, inputs.withSavedConfig))
+ }
override protected def hideFromUserProfile(snippetId: SnippetId): Future[Unit] = Future {
for {
old <- snippets.get(snippetId)
} yield snippets.update(snippetId, old.copy(inputs = old.inputs.copy(isShowingInUserProfile = false)))
}
+
}
diff --git a/storage/src/main/scala/com.olegych.scastie.storage/inmemory/InMemoryUsersContainer.scala b/storage/src/main/scala/com.olegych.scastie.storage/inmemory/InMemoryUsersContainer.scala
index f348fbbd1..cbb2fd932 100644
--- a/storage/src/main/scala/com.olegych.scastie.storage/inmemory/InMemoryUsersContainer.scala
+++ b/storage/src/main/scala/com.olegych.scastie.storage/inmemory/InMemoryUsersContainer.scala
@@ -1,12 +1,12 @@
package com.olegych.scastie.storage.inmemory
+import scala.collection.mutable
+import scala.concurrent.Future
+
import com.olegych.scastie.storage.PolicyAcceptance
import com.olegych.scastie.storage.UserLogin
import com.olegych.scastie.storage.UsersContainer
-import scala.collection.mutable
-import scala.concurrent.Future
-
trait InMemoryUsersContainer extends UsersContainer {
val users: mutable.Set[PolicyAcceptance] = mutable.Set[PolicyAcceptance]()
@@ -29,4 +29,5 @@ trait InMemoryUsersContainer extends UsersContainer {
// All user containers will be removed after said period of time
maybeUser.map(_.acceptedPrivacyPolicy).getOrElse(true)
}
+
}
diff --git a/storage/src/main/scala/com.olegych.scastie.storage/mongodb/GenericMongoContainer.scala b/storage/src/main/scala/com.olegych.scastie.storage/mongodb/GenericMongoContainer.scala
index 346f03bdc..ca74ec04a 100644
--- a/storage/src/main/scala/com.olegych.scastie.storage/mongodb/GenericMongoContainer.scala
+++ b/storage/src/main/scala/com.olegych.scastie.storage/mongodb/GenericMongoContainer.scala
@@ -23,4 +23,5 @@ trait GenericMongoContainer {
): Option[T] = {
Json.parse(obj.toJson()).asOpt[T]
}
+
}
diff --git a/storage/src/main/scala/com.olegych.scastie.storage/mongodb/MongoDBContainer.scala b/storage/src/main/scala/com.olegych.scastie.storage/mongodb/MongoDBContainer.scala
index 3eff97f67..006319801 100644
--- a/storage/src/main/scala/com.olegych.scastie.storage/mongodb/MongoDBContainer.scala
+++ b/storage/src/main/scala/com.olegych.scastie.storage/mongodb/MongoDBContainer.scala
@@ -1,13 +1,14 @@
package com.olegych.scastie.storage.mongodb
+import scala.concurrent.ExecutionContext
+
import com.typesafe.config.ConfigFactory
import org.mongodb.scala._
-import scala.concurrent.ExecutionContext
-
class MongoDBContainer(defaultConfig: Boolean = false)(
implicit val ec: ExecutionContext
-) extends MongoDBUsersContainer with MongoDBSnippetsContainer {
+) extends MongoDBUsersContainer
+ with MongoDBSnippetsContainer {
val mongoUri = {
if (defaultConfig) s"mongodb://localhost:27017/scastie"
diff --git a/storage/src/main/scala/com.olegych.scastie.storage/mongodb/MongoDBSnippetsContainer.scala b/storage/src/main/scala/com.olegych.scastie.storage/mongodb/MongoDBSnippetsContainer.scala
index 0d644990e..b0b44f5e3 100644
--- a/storage/src/main/scala/com.olegych.scastie.storage/mongodb/MongoDBSnippetsContainer.scala
+++ b/storage/src/main/scala/com.olegych.scastie.storage/mongodb/MongoDBSnippetsContainer.scala
@@ -1,36 +1,43 @@
package com.olegych.scastie.storage.mongodb
+import java.lang.System.{lineSeparator => nl}
+import scala.concurrent.duration._
+import scala.concurrent.Await
+import scala.concurrent.Future
+
import com.olegych.scastie.api._
import com.olegych.scastie.storage._
import org.mongodb.scala._
+import org.mongodb.scala.model._
import org.mongodb.scala.model.Filters._
import org.mongodb.scala.model.Updates._
-import org.mongodb.scala.model._
-
-import java.lang.System.{lineSeparator => nl}
-import scala.concurrent.Await
-import scala.concurrent.Future
-import scala.concurrent.duration._
-
trait MongoDBSnippetsContainer extends SnippetsContainer with GenericMongoContainer {
+
lazy val snippets = {
val db = database.getCollection[Document]("snippets")
- Await.result(db.createIndex(Indexes.ascending("simpleSnippetId", "oldId"), IndexOptions().unique(true)).head(), Duration.Inf)
- Await.result(Future.sequence(Seq(
- Indexes.hashed("simpleSnippetId"),
- Indexes.hashed("oldId"),
- Indexes.hashed("user"),
- Indexes.hashed("snippetId.user.login"),
- Indexes.hashed("inputs.isShowingInUserProfile"),
- Indexes.hashed("time")
- ).map(db.createIndex(_).head())), Duration.Inf)
+ Await.result(
+ db.createIndex(Indexes.ascending("simpleSnippetId", "oldId"), IndexOptions().unique(true)).head(),
+ Duration.Inf
+ )
+ Await.result(
+ Future.sequence(
+ Seq(
+ Indexes.hashed("simpleSnippetId"),
+ Indexes.hashed("oldId"),
+ Indexes.hashed("user"),
+ Indexes.hashed("snippetId.user.login"),
+ Indexes.hashed("inputs.isShowingInUserProfile"),
+ Indexes.hashed("time")
+ ).map(db.createIndex(_).head())
+ ),
+ Duration.Inf
+ )
db
}
-
def toMongoSnippet(snippetId: SnippetId, inputs: Inputs): MongoSnippet = MongoSnippet(
simpleSnippetId = snippetId.url,
user = snippetId.user.map(_.login),
@@ -141,7 +148,7 @@ trait MongoDBSnippetsContainer extends SnippetsContainer with GenericMongoContai
.map(_.flatMap(fromBson[MongoSnippet]).map(_.toFetchResult))
override def removeUserSnippets(user: UserLogin): Future[Boolean] = {
- val query = or(Document("user" -> user.login), Document("snippetId.user.login" -> user.login))
+ val query = or(Document("user" -> user.login), Document("snippetId.user.login" -> user.login))
val deletion = snippets.deleteMany(query).head().map(_.wasAcknowledged)
lazy val validation = listSnippets(user).map(_.isEmpty)
diff --git a/storage/src/main/scala/com.olegych.scastie.storage/mongodb/MongoDBStoredClasses.scala b/storage/src/main/scala/com.olegych.scastie.storage/mongodb/MongoDBStoredClasses.scala
index 1f3d48f82..0f4cd8e5b 100644
--- a/storage/src/main/scala/com.olegych.scastie.storage/mongodb/MongoDBStoredClasses.scala
+++ b/storage/src/main/scala/com.olegych.scastie.storage/mongodb/MongoDBStoredClasses.scala
@@ -1,8 +1,8 @@
package com.olegych.scastie.storage
-import play.api.libs.json.OFormat
-import play.api.libs.json.Json
import com.olegych.scastie.api._
+import play.api.libs.json.Json
+import play.api.libs.json.OFormat
sealed trait BaseMongoSnippet {
def snippetId: SnippetId
@@ -11,9 +11,9 @@ sealed trait BaseMongoSnippet {
}
case class ShortMongoSnippet(
- snippetId: SnippetId,
- inputs: ShortInputs,
- time: Long
+ snippetId: SnippetId,
+ inputs: ShortInputs,
+ time: Long
) extends BaseMongoSnippet
object ShortMongoSnippet {
@@ -27,15 +27,15 @@ object PolicyAcceptance {
}
case class MongoSnippet(
- simpleSnippetId: String,
- user: Option[String],
- snippetId: SnippetId,
- oldId: Long,
- inputs: Inputs,
- progresses: List[SnippetProgress],
- scalaJsContent: String,
- scalaJsSourceMapContent: String,
- time: Long
+ simpleSnippetId: String,
+ user: Option[String],
+ snippetId: SnippetId,
+ oldId: Long,
+ inputs: Inputs,
+ progresses: List[SnippetProgress],
+ scalaJsContent: String,
+ scalaJsSourceMapContent: String,
+ time: Long
) extends BaseMongoSnippet {
def toFetchResult: FetchResult = FetchResult.create(inputs, progresses)
}
diff --git a/storage/src/main/scala/com.olegych.scastie.storage/mongodb/MongoDBUsersContainer.scala b/storage/src/main/scala/com.olegych.scastie.storage/mongodb/MongoDBUsersContainer.scala
index 5bb67de3d..b2d43a75f 100644
--- a/storage/src/main/scala/com.olegych.scastie.storage/mongodb/MongoDBUsersContainer.scala
+++ b/storage/src/main/scala/com.olegych.scastie.storage/mongodb/MongoDBUsersContainer.scala
@@ -1,15 +1,16 @@
package com.olegych.scastie.storage.mongodb
+import scala.concurrent.duration._
+import scala.concurrent.Await
+import scala.concurrent.Future
+
import com.olegych.scastie.storage._
import org.mongodb.scala._
-import org.mongodb.scala.model.Updates._
import org.mongodb.scala.model._
-
-import scala.concurrent.Await
-import scala.concurrent.Future
-import scala.concurrent.duration._
+import org.mongodb.scala.model.Updates._
trait MongoDBUsersContainer extends UsersContainer with GenericMongoContainer {
+
lazy val users = {
val db = database.getCollection[Document]("users")
Await.result(db.createIndex(Indexes.ascending("user"), IndexOptions().unique(true)).head(), Duration.Inf)
diff --git a/storage/src/test/scala/com.olegych.scastie.storage/ContainerTest.scala b/storage/src/test/scala/com.olegych.scastie.storage/ContainerTest.scala
index 93eeac1ac..01796ee9f 100644
--- a/storage/src/test/scala/com.olegych.scastie.storage/ContainerTest.scala
+++ b/storage/src/test/scala/com.olegych.scastie.storage/ContainerTest.scala
@@ -1,38 +1,37 @@
package com.olegych.scastie.storage
-import com.olegych.scastie.api._
-import com.olegych.scastie.storage.filesystem.FilesystemContainer
-import com.olegych.scastie.storage.mongodb.MongoDBContainer
-import org.scalatest.BeforeAndAfterAll
-import org.scalatest.OptionValues
-import org.scalatest.funsuite.AnyFunSuite
-
import java.io.IOException
+import java.nio.file.attribute.BasicFileAttributes
import java.nio.file.FileVisitResult
import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.SimpleFileVisitor
-import java.nio.file.attribute.BasicFileAttributes
import java.util.concurrent.Executors
+import scala.concurrent.duration._
import scala.concurrent.Await
import scala.concurrent.ExecutionContext
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Future
-import scala.concurrent.duration._
import scala.util.Random
+import com.olegych.scastie.api._
+import com.olegych.scastie.storage.filesystem.FilesystemContainer
+import com.olegych.scastie.storage.mongodb.MongoDBContainer
+import org.scalatest.funsuite.AnyFunSuite
+import org.scalatest.BeforeAndAfterAll
+import org.scalatest.OptionValues
+
class ContainerTest extends AnyFunSuite with BeforeAndAfterAll with OptionValues {
val mongo = sys.props.get("SnippetsContainerTest.mongo").flatMap(_.toBooleanOption).contains(true)
println(s"ContainerTest using mongodb: $mongo")
- val root = Files.createTempDirectory("test")
+ val root = Files.createTempDirectory("test")
val oldRoot = Files.createTempDirectory("old-test")
private val testContainer: SnippetsContainer with UsersContainer = {
- if (mongo)
- new MongoDBContainer(defaultConfig = true)
+ if (mongo) new MongoDBContainer(defaultConfig = true)
else {
new FilesystemContainer(root, oldRoot)(
- ExecutionContext.fromExecutorService(Executors.newSingleThreadExecutor())
+ ExecutionContext.fromExecutorService(Executors.newSingleThreadExecutor())
)
}
}
@@ -64,9 +63,8 @@ class ContainerTest extends AnyFunSuite with BeforeAndAfterAll with OptionValues
}
test("create snippet with logged in user") {
- val bob = "bob"
- val snippetId =
- testContainer.create(Inputs.default, user = Some(UserLogin(bob)))
+ val bob = "bob"
+ val snippetId = testContainer.create(Inputs.default, user = Some(UserLogin(bob)))
assert(snippetId.await.user.get.login == bob)
}
@@ -77,22 +75,19 @@ class ContainerTest extends AnyFunSuite with BeforeAndAfterAll with OptionValues
}
test("create then read") {
- val inputs = Inputs.default
+ val inputs = Inputs.default
val snippetId = testContainer.create(inputs, user = None).await
- val result = testContainer.readSnippet(snippetId).await
+ val result = testContainer.readSnippet(snippetId).await
assert(result.value.inputs == inputs.withSavedConfig)
}
test("fork") {
- val inputs =
- Inputs.default.copy(code = "source", isShowingInUserProfile = true)
+ val inputs = Inputs.default.copy(code = "source", isShowingInUserProfile = true)
val snippetId = testContainer.save(inputs, user = None).await
- val forkedInputs =
- Inputs.default.copy(code = "forked", isShowingInUserProfile = true)
- val forkedSnippetId =
- testContainer.fork(snippetId, forkedInputs, user = None).await
+ val forkedInputs = Inputs.default.copy(code = "forked", isShowingInUserProfile = true)
+ val forkedSnippetId = testContainer.fork(snippetId, forkedInputs, user = None).await
val forkedBis = testContainer.readSnippet(forkedSnippetId).await.get
@@ -101,21 +96,22 @@ class ContainerTest extends AnyFunSuite with BeforeAndAfterAll with OptionValues
}
test("update") {
- val user = UserLogin("github-user-update" + Random.nextInt())
- val inputs1 =
- Inputs.default.copy(code = "inputs1").copy(isShowingInUserProfile = true)
+ val user = UserLogin("github-user-update" + Random.nextInt())
+ val inputs1 = Inputs.default.copy(code = "inputs1").copy(isShowingInUserProfile = true)
val snippetId1 = testContainer.save(inputs1, Some(user)).await
assert(snippetId1.user.get.update == 0)
- val inputs2 =
- Inputs.default.copy(code = "inputs2").copy(isShowingInUserProfile = true)
+ val inputs2 = Inputs.default.copy(code = "inputs2").copy(isShowingInUserProfile = true)
val snippetId2 = testContainer.update(snippetId1, inputs2).await.get
assert(snippetId2.user.get.update == 1, "we get a new update id")
val readInputs1 = testContainer.readSnippet(snippetId1).await.get.inputs
val readInputs2 = testContainer.readSnippet(snippetId2).await.get.inputs
- assert(readInputs1 == inputs1.copy(isShowingInUserProfile = false).withSavedConfig, "we don't mutate previous input")
+ assert(
+ readInputs1 == inputs1.copy(isShowingInUserProfile = false).withSavedConfig,
+ "we don't mutate previous input"
+ )
assert(readInputs2 == inputs2.copy(forked = Some(snippetId1)).withSavedConfig, "we update forked")
val snippets = testContainer.listSnippets(user).await
@@ -123,7 +119,7 @@ class ContainerTest extends AnyFunSuite with BeforeAndAfterAll with OptionValues
}
test("listSnippets") {
- val user = UserLogin("github-user-list" + Random.nextInt())
+ val user = UserLogin("github-user-list" + Random.nextInt())
val user2 = UserLogin("github-user-list2" + Random.nextInt())
val inputs1 = Inputs.default.copy(code = "inputs1")
@@ -138,8 +134,7 @@ class ContainerTest extends AnyFunSuite with BeforeAndAfterAll with OptionValues
val user2inputs = Inputs.default.copy(code = "inputs3")
testContainer.save(user2inputs, Some(user2)).await
- val inputs4 =
- Inputs.default.copy(code = "inputs4", isShowingInUserProfile = false)
+ val inputs4 = Inputs.default.copy(code = "inputs4", isShowingInUserProfile = false)
testContainer.create(inputs4, Some(user)).await
val snippets = testContainer.listSnippets(user).await
@@ -151,16 +146,16 @@ class ContainerTest extends AnyFunSuite with BeforeAndAfterAll with OptionValues
test("delete") {
val user = UserLogin("github-user-delete" + Random.nextInt())
- val inputs1 = Inputs.default.copy(code = "inputs1")
+ val inputs1 = Inputs.default.copy(code = "inputs1")
val snippetId1 = testContainer.save(inputs1, Some(user)).await
val inputs1U = Inputs.default.copy(code = "inputs1 updated")
testContainer.update(snippetId1, inputs1U).await.get
- val inputs2 = Inputs.default.copy(code = "inputs2")
+ val inputs2 = Inputs.default.copy(code = "inputs2")
val snippetId2 = testContainer.save(inputs2, Some(user)).await
- val inputs2U = Inputs.default.copy(code = "inputs2 updated")
+ val inputs2U = Inputs.default.copy(code = "inputs2 updated")
val snippetId2U = testContainer.update(snippetId2, inputs2U).await.get
assert(testContainer.listSnippets(user).await.size == 2)
@@ -174,9 +169,9 @@ class ContainerTest extends AnyFunSuite with BeforeAndAfterAll with OptionValues
}
test("appendOutput") {
- val inputs = Inputs.default
+ val inputs = Inputs.default
val snippetId = testContainer.create(inputs, user = None).await
- val progress = SnippetProgress.default.copy(snippetId = Some(snippetId))
+ val progress = SnippetProgress.default.copy(snippetId = Some(snippetId))
testContainer.appendOutput(progress)
val result = testContainer.readSnippet(snippetId).await
@@ -186,13 +181,13 @@ class ContainerTest extends AnyFunSuite with BeforeAndAfterAll with OptionValues
test("deleteAllSnippets") {
val user = UserLogin("github-user-delete" + Random.nextInt())
- val inputs1 = Inputs.default.copy(code = "inputs1")
+ val inputs1 = Inputs.default.copy(code = "inputs1")
val snippetId1 = testContainer.save(inputs1, Some(user)).await
- val inputs2 = Inputs.default.copy(code = "inputs2")
+ val inputs2 = Inputs.default.copy(code = "inputs2")
val snippetId2 = testContainer.save(inputs2, Some(user)).await
- val inputs2U = Inputs.default.copy(code = "inputs2 updated")
+ val inputs2U = Inputs.default.copy(code = "inputs2 updated")
val snippetId2U = testContainer.update(snippetId2, inputs2U).await.get
assert(testContainer.listSnippets(user).await.size == 2)
@@ -216,32 +211,41 @@ class ContainerTest extends AnyFunSuite with BeforeAndAfterAll with OptionValues
}
test("add new user") {
- ensureUserCleanup("bob", { username =>
- val snippetId = testContainer.addNewUser(UserLogin(username)).await
- assert(snippetId)
- })
+ ensureUserCleanup(
+ "bob",
+ { username =>
+ val snippetId = testContainer.addNewUser(UserLogin(username)).await
+ assert(snippetId)
+ }
+ )
}
test("get user privacy policy acceptance") {
- ensureUserCleanup("bob", { username =>
- val snippetId = testContainer.addNewUser(UserLogin(username)).await
- val response = testContainer.getPrivacyPolicyResponse(UserLogin(username)).await
- assert(testContainer.deleteUser(UserLogin(username)).await == true)
- })
+ ensureUserCleanup(
+ "bob",
+ { username =>
+ val snippetId = testContainer.addNewUser(UserLogin(username)).await
+ val response = testContainer.getPrivacyPolicyResponse(UserLogin(username)).await
+ assert(testContainer.deleteUser(UserLogin(username)).await == true)
+ }
+ )
}
test("set user privacy policy acceptance") {
- ensureUserCleanup("bob", { username =>
- val snippetId = testContainer.addNewUser(UserLogin(username)).await
- val updatePrivacyPolicy = testContainer.setPrivacyPolicyResponse(UserLogin(username), false).await
- val response = testContainer.getPrivacyPolicyResponse(UserLogin(username)).await
- assert(response == false)
- })
+ ensureUserCleanup(
+ "bob",
+ { username =>
+ val snippetId = testContainer.addNewUser(UserLogin(username)).await
+ val updatePrivacyPolicy = testContainer.setPrivacyPolicyResponse(UserLogin(username), false).await
+ val response = testContainer.getPrivacyPolicyResponse(UserLogin(username)).await
+ assert(response == false)
+ }
+ )
}
test("remove user from privacy policy list") {
- val username = "bob"
- val snippetId = testContainer.addNewUser(UserLogin(username)).await
+ val username = "bob"
+ val snippetId = testContainer.addNewUser(UserLogin(username)).await
val removeUser = testContainer.deleteUser(UserLogin(username)).await
assert(removeUser == true)
}
diff --git a/utils/src/main/scala/com.olegych.scastie/util/Base64UUID.scala b/utils/src/main/scala/com.olegych.scastie/util/Base64UUID.scala
index e94124cd8..38bd49feb 100644
--- a/utils/src/main/scala/com.olegych.scastie/util/Base64UUID.scala
+++ b/utils/src/main/scala/com.olegych.scastie/util/Base64UUID.scala
@@ -4,12 +4,12 @@ import java.nio.ByteBuffer
import java.util.{Base64, UUID}
object Base64UUID {
+
// example output: GGdknrcEQVu3elXyboKcYQ
def create: String = {
def toBase64(uuid: UUID): String = {
- val (high, low) =
- (uuid.getMostSignificantBits, uuid.getLeastSignificantBits)
- val buffer = ByteBuffer.allocate(java.lang.Long.BYTES * 2)
+ val (high, low) = (uuid.getMostSignificantBits, uuid.getLeastSignificantBits)
+ val buffer = ByteBuffer.allocate(java.lang.Long.BYTES * 2)
buffer.putLong(high)
buffer.putLong(low)
val encoded = Base64.getMimeEncoder.encodeToString(buffer.array())
@@ -17,7 +17,7 @@ object Base64UUID {
}
var res: String = null
- val allowed = ('a' to 'z').toSet ++ ('A' to 'Z').toSet ++ ('0' to '9').toSet
+ val allowed = ('a' to 'z').toSet ++ ('A' to 'Z').toSet ++ ('0' to '9').toSet
while (res == null || res.exists(c => !allowed.contains(c))) {
val uuid = java.util.UUID.randomUUID()
@@ -26,4 +26,5 @@ object Base64UUID {
res
}
+
}
diff --git a/utils/src/main/scala/com.olegych.scastie/util/BlockingProcess.scala b/utils/src/main/scala/com.olegych.scastie/util/BlockingProcess.scala
index f8e48425a..5f5e178fe 100644
--- a/utils/src/main/scala/com.olegych.scastie/util/BlockingProcess.scala
+++ b/utils/src/main/scala/com.olegych.scastie/util/BlockingProcess.scala
@@ -1,20 +1,20 @@
/**
- * Copyright (C) 2009-2014 Typesafe Inc.
- */
+ * Copyright (C) 2009-2014 Typesafe Inc.
+ */
package akka.contrib.process
-import akka.actor.{Actor, ActorLogging, ActorRef, NoSerializationVerificationNeeded, Props, SupervisorStrategy, Terminated}
-import akka.stream.{ActorAttributes, IOResult}
-import akka.stream.scaladsl.{Sink, Source, StreamConverters}
-import akka.util.{ByteString, Helpers}
import java.io.File
import java.lang.{Process => JavaProcess, ProcessBuilder => JavaProcessBuilder}
import java.util.concurrent.TimeUnit
-
import scala.collection.immutable
-import scala.concurrent.{Future, blocking}
+import scala.concurrent.{blocking, Future}
import scala.concurrent.duration.Duration
+import akka.actor.{Actor, ActorLogging, ActorRef, NoSerializationVerificationNeeded, Props, SupervisorStrategy, Terminated}
+import akka.stream.{ActorAttributes, IOResult}
+import akka.stream.scaladsl.{Sink, Source, StreamConverters}
+import akka.util.{ByteString, Helpers}
+
object BlockingProcess {
def getPid(process: JavaProcess): Long = {
@@ -22,143 +22,151 @@ object BlockingProcess {
}
/**
- * The configuration key to use in order to override the dispatcher used for blocking IO.
- */
- final val BlockingIODispatcherId =
- "akka.process.blocking-process.blocking-io-dispatcher-id"
+ * The configuration key to use in order to override the dispatcher used for blocking IO.
+ */
+ final val BlockingIODispatcherId = "akka.process.blocking-process.blocking-io-dispatcher-id"
/**
- * Sent to the receiver on startup - specifies the streams used for managing input, output and error respectively.
- * This message should only be received by the parent of the BlockingProcess and should not be passed across the
- * JVM boundary (the publishers are not serializable).
- *
- * @param stdin a `akka.stream.scaladsl.Sink[ByteString, Future[IOResult]]` for the standard input stream of the process
- * @param stdout a `akka.stream.scaladsl.Source[ByteString, Future[IOResult]]` for the standard output stream of the process
- * @param stderr a `akka.stream.scaladsl.Source[ByteString, Future[IOResult]]` for the standard error stream of the process
- */
- case class Started(pid: Option[Long],
- stdin: Sink[ByteString, Future[IOResult]],
- stdout: Source[ByteString, Future[IOResult]],
- stderr: Source[ByteString, Future[IOResult]])
- extends NoSerializationVerificationNeeded
+ * Sent to the receiver on startup - specifies the streams used for managing input, output and error respectively.
+ * This message should only be received by the parent of the BlockingProcess and should not be passed across the JVM
+ * boundary (the publishers are not serializable).
+ *
+ * @param stdin
+ * a `akka.stream.scaladsl.Sink[ByteString, Future[IOResult]]` for the standard input stream of the process
+ * @param stdout
+ * a `akka.stream.scaladsl.Source[ByteString, Future[IOResult]]` for the standard output stream of the process
+ * @param stderr
+ * a `akka.stream.scaladsl.Source[ByteString, Future[IOResult]]` for the standard error stream of the process
+ */
+ case class Started(
+ pid: Option[Long],
+ stdin: Sink[ByteString, Future[IOResult]],
+ stdout: Source[ByteString, Future[IOResult]],
+ stderr: Source[ByteString, Future[IOResult]]
+ ) extends NoSerializationVerificationNeeded
/**
- * Sent to the receiver after the process has exited.
- *
- * @param exitValue the exit value of the process
- */
+ * Sent to the receiver after the process has exited.
+ *
+ * @param exitValue
+ * the exit value of the process
+ */
case class Exited(exitValue: Int)
/**
- * Send a request to destroy the process.
- * On POSIX, this sends a SIGTERM, but implementation is platform specific.
- */
+ * Send a request to destroy the process. On POSIX, this sends a SIGTERM, but implementation is platform specific.
+ */
case object Destroy
/**
- * Send a request to forcibly destroy the process.
- * On POSIX, this sends a SIGKILL, but implementation is platform specific.
- */
+ * Send a request to forcibly destroy the process. On POSIX, this sends a SIGKILL, but implementation is platform
+ * specific.
+ */
case object DestroyForcibly
/**
- * Sent if stdin from the process is terminated
- */
+ * Sent if stdin from the process is terminated
+ */
case object StdinTerminated
/**
- * Sent if stdout from the process is terminated
- */
+ * Sent if stdout from the process is terminated
+ */
case object StdoutTerminated
/**
- * Sent if stderr from the process is terminated
- */
+ * Sent if stderr from the process is terminated
+ */
case object StderrTerminated
/**
- * Create Props for a [[BlockingProcess]] actor.
- *
- * @param command signifies the program to be executed and its optional arguments
- * @param workingDir the working directory for the process; default is the current working directory
- * @param environment the environment for the process; default is `Map.emtpy`
- * @param stdioTimeout the amount of time to tolerate waiting for a process to communicate back to this actor
- * @return Props for a [[BlockingProcess]] actor
- */
- def props(command: immutable.Seq[String],
- workingDir: File = new File(System.getProperty("user.dir")),
- environment: Map[String, String] = Map.empty,
- stdioTimeout: Duration = Duration.Undefined) =
- Props(new BlockingProcess(command, workingDir, environment, stdioTimeout))
+ * Create Props for a [[BlockingProcess]] actor.
+ *
+ * @param command
+ * signifies the program to be executed and its optional arguments
+ * @param workingDir
+ * the working directory for the process; default is the current working directory
+ * @param environment
+ * the environment for the process; default is `Map.emtpy`
+ * @param stdioTimeout
+ * the amount of time to tolerate waiting for a process to communicate back to this actor
+ * @return
+ * Props for a [[BlockingProcess]] actor
+ */
+ def props(
+ command: immutable.Seq[String],
+ workingDir: File = new File(System.getProperty("user.dir")),
+ environment: Map[String, String] = Map.empty,
+ stdioTimeout: Duration = Duration.Undefined
+ ) = Props(new BlockingProcess(command, workingDir, environment, stdioTimeout))
private def prepareCommand(command: Seq[String]) =
if (Helpers.isWindows) List("cmd", "/c") ++ (command map winQuote)
else command
/**
- * This quoting functionality is as recommended per http://bugs.java.com/view_bug.do?bug_id=6511002
- * The JDK can't change due to its backward compatibility requirements, but we have no such constraint
- * here. Args should be able to be expressed consistently by the user of our API no matter whether
- * execution is on Windows or not.
- *
- * @param s command string to be quoted
- * @return quoted string
- */
+ * This quoting functionality is as recommended per http://bugs.java.com/view_bug.do?bug_id=6511002 The JDK can't
+ * change due to its backward compatibility requirements, but we have no such constraint here. Args should be able to
+ * be expressed consistently by the user of our API no matter whether execution is on Windows or not.
+ *
+ * @param s
+ * command string to be quoted
+ * @return
+ * quoted string
+ */
private def winQuote(s: String): String = {
- def needsQuoting(s: String) =
- s.isEmpty || (s exists (
- c => c == ' ' || c == '\t' || c == '\\' || c == '"'
- ))
+ def needsQuoting(s: String) = s.isEmpty || (s exists (c => c == ' ' || c == '\t' || c == '\\' || c == '"'))
if (needsQuoting(s)) {
val quoted = s
.replaceAll("""([\\]*)"""", """$1$1\\"""")
.replaceAll("""([\\]*)\z""", "$1$1")
s""""$quoted""""
- } else
- s
+ } else s
}
+
}
/**
- * This actor uses the JDK process API. As such, more memory given that more threads are consumed. Favor the
- * [[NonBlockingProcess]] actor unless you *need* to use the JDK.
- *
- * BlockingProcess encapsulates an operating system process and its ability to be communicated with via stdio i.e.
- * stdin, stdout and stderr. The reactive streams for stdio are communicated in a BlockingProcess.Started event
- * upon the actor being established. The parent actor is then subsequently streamed
- * stdout and stderr events. When the process exists (determined by periodically polling process.isAlive()) then
- * the process's exit code is communicated to the receiver in a BlockingProcess.Exited event.
- *
- * A dispatcher as indicated by the "akka.process.blocking-process.blocking-io-dispatcher-id" setting is used
- * internally by the actor as various JDK calls are made which can block.
- */
-class BlockingProcess(command: immutable.Seq[String], directory: File, environment: Map[String, String], stdioTimeout: Duration)
- extends Actor
- with ActorLogging {
+ * This actor uses the JDK process API. As such, more memory given that more threads are consumed. Favor the
+ * [[NonBlockingProcess]] actor unless you *need* to use the JDK.
+ *
+ * BlockingProcess encapsulates an operating system process and its ability to be communicated with via stdio i.e.
+ * stdin, stdout and stderr. The reactive streams for stdio are communicated in a BlockingProcess.Started event upon
+ * the actor being established. The parent actor is then subsequently streamed stdout and stderr events. When the
+ * process exists (determined by periodically polling process.isAlive()) then the process's exit code is communicated
+ * to the receiver in a BlockingProcess.Exited event.
+ *
+ * A dispatcher as indicated by the "akka.process.blocking-process.blocking-io-dispatcher-id" setting is used
+ * internally by the actor as various JDK calls are made which can block.
+ */
+class BlockingProcess(
+ command: immutable.Seq[String],
+ directory: File,
+ environment: Map[String, String],
+ stdioTimeout: Duration
+) extends Actor
+ with ActorLogging {
- import BlockingProcess._
import context.dispatcher
+ import BlockingProcess._
- override val supervisorStrategy: SupervisorStrategy =
- SupervisorStrategy.stoppingStrategy
+ override val supervisorStrategy: SupervisorStrategy = SupervisorStrategy.stoppingStrategy
override def preStart(): Unit = {
println("preStart")
val process: JavaProcess = {
import scala.jdk.CollectionConverters._
val preparedCommand = prepareCommand(command)
- val pb = new JavaProcessBuilder(preparedCommand.asJava)
+ val pb = new JavaProcessBuilder(preparedCommand.asJava)
pb.environment().putAll(environment.asJava)
pb.directory(directory)
pb.start()
}
- val blockingIODispatcherId =
- context.system.settings.config.getString(BlockingIODispatcherId)
+ val blockingIODispatcherId = context.system.settings.config.getString(BlockingIODispatcherId)
try {
- val selfDispatcherAttribute =
- ActorAttributes.dispatcher(blockingIODispatcherId)
+ val selfDispatcherAttribute = ActorAttributes.dispatcher(blockingIODispatcherId)
val stdin = StreamConverters
.fromOutputStream(() => process.getOutputStream(), autoFlush = true)
@@ -200,8 +208,7 @@ class BlockingProcess(command: immutable.Seq[String], directory: File, environme
case DestroyForcibly =>
log.debug("Received request to forcibly destroy the process.")
tellDestroyer(ProcessDestroyer.DestroyForcibly)
- case Terminated(_) =>
- context.stop(self)
+ case Terminated(_) => context.stop(self)
case StdinTerminated =>
log.debug("Stdin was terminated")
tellDestroyer(ProcessDestroyer.Inspect)
@@ -213,34 +220,31 @@ class BlockingProcess(command: immutable.Seq[String], directory: File, environme
tellDestroyer(ProcessDestroyer.Inspect)
}
- private def tellDestroyer(msg: Any) =
- context.child("process-destroyer").foreach(_ ! msg)
+ private def tellDestroyer(msg: Any) = context.child("process-destroyer").foreach(_ ! msg)
}
private object ProcessDestroyer {
/**
- * The configuration key to use for the inspection interval.
- */
- final val InspectionInterval =
- "akka.process.blocking-process.inspection-interval"
+ * The configuration key to use for the inspection interval.
+ */
+ final val InspectionInterval = "akka.process.blocking-process.inspection-interval"
/**
- * Inspect the Process to ensure it is still alive. This is necessary because
- * a process can exit without its stdout/stderr file handles being closed, for
- * instance if a process forks and a child continues to run when it dies,
- * it will have a reference to those handles.
- */
+ * Inspect the Process to ensure it is still alive. This is necessary because a process can exit without its
+ * stdout/stderr file handles being closed, for instance if a process forks and a child continues to run when it
+ * dies, it will have a reference to those handles.
+ */
case object Inspect
/**
- * Request that process.destroy() be called
- */
+ * Request that process.destroy() be called
+ */
case object Destroy
/**
- * Request that process.destroyForcibly() be called
- */
+ * Request that process.destroyForcibly() be called
+ */
case object DestroyForcibly
def props(process: JavaProcess, exitValueReceiver: ActorRef): Props =
@@ -248,14 +252,13 @@ private object ProcessDestroyer {
}
private class ProcessDestroyer(process: JavaProcess, exitValueReceiver: ActorRef) extends Actor with ActorLogging {
- import ProcessDestroyer._
import context.dispatcher
+ import ProcessDestroyer._
- private val inspectionInterval =
- Duration(
- context.system.settings.config.getDuration(InspectionInterval).toMillis,
- TimeUnit.MILLISECONDS
- )
+ private val inspectionInterval = Duration(
+ context.system.settings.config.getDuration(InspectionInterval).toMillis,
+ TimeUnit.MILLISECONDS
+ )
private val inspectionTick =
context.system.scheduler.scheduleAtFixedRate(inspectionInterval, inspectionInterval, self, Inspect)
@@ -274,12 +277,9 @@ private class ProcessDestroyer(process: JavaProcess, exitValueReceiver: ActorRef
}
override def receive = {
- case Destroy =>
- blocking(pkill())
- case DestroyForcibly =>
- blocking(pkill())
- case Inspect =>
- if (!process.isAlive) {
+ case Destroy => blocking(pkill())
+ case DestroyForcibly => blocking(pkill())
+ case Inspect => if (!process.isAlive) {
log.debug("Process has terminated, stopping self")
context.stop(self)
}
@@ -294,4 +294,5 @@ private class ProcessDestroyer(process: JavaProcess, exitValueReceiver: ActorRef
}
exitValueReceiver ! BlockingProcess.Exited(exitValue)
}
+
}
diff --git a/utils/src/main/scala/com.olegych.scastie/util/GraphStageForwarder.scala b/utils/src/main/scala/com.olegych.scastie/util/GraphStageForwarder.scala
index 4975c8bda..58a639016 100644
--- a/utils/src/main/scala/com.olegych.scastie/util/GraphStageForwarder.scala
+++ b/utils/src/main/scala/com.olegych.scastie/util/GraphStageForwarder.scala
@@ -1,15 +1,15 @@
package com.olegych.scastie.util
+import scala.reflect.runtime.universe._
+
import akka.actor.ActorRef
import akka.stream.{Attributes, Outlet, SourceShape}
import akka.stream.stage.{GraphStage, GraphStageLogic}
-import scala.reflect.runtime.universe._
-
class GraphStageForwarder[T: TypeTag, U: TypeTag](
- outletName: String,
- coordinator: ActorRef,
- graphId: U
+ outletName: String,
+ coordinator: ActorRef,
+ graphId: U
) extends GraphStage[SourceShape[T]] {
val out: Outlet[T] = Outlet(outletName)
override val shape = SourceShape[T](out)
diff --git a/utils/src/main/scala/com.olegych.scastie/util/GraphStageLogicForwarder.scala b/utils/src/main/scala/com.olegych.scastie/util/GraphStageLogicForwarder.scala
index 3f602211b..a9fdf553b 100644
--- a/utils/src/main/scala/com.olegych.scastie/util/GraphStageLogicForwarder.scala
+++ b/utils/src/main/scala/com.olegych.scastie/util/GraphStageLogicForwarder.scala
@@ -1,14 +1,18 @@
package com.olegych.scastie.util
+import scala.collection.mutable.{Queue => MQueue}
+import scala.reflect.runtime.universe._
+
import akka.actor.ActorRef
import akka.stream.{Outlet, SourceShape}
import akka.stream.stage.{GraphStageLogic, OutHandler}
-import scala.collection.mutable.{Queue => MQueue}
-import scala.reflect.runtime.universe._
-
-class GraphStageLogicForwarder[T: TypeTag, U: TypeTag](out: Outlet[T], shape: SourceShape[T], coordinator: ActorRef, graphId: U)
- extends GraphStageLogic(shape) {
+class GraphStageLogicForwarder[T: TypeTag, U: TypeTag](
+ out: Outlet[T],
+ shape: SourceShape[T],
+ coordinator: ActorRef,
+ graphId: U
+) extends GraphStageLogic(shape) {
setHandler(
out,
@@ -26,15 +30,12 @@ class GraphStageLogicForwarder[T: TypeTag, U: TypeTag](out: Outlet[T], shape: So
private val buffer = MQueue.empty[T]
- private def deliver(): Unit =
- if (isAvailable(out) && buffer.nonEmpty)
- push[T](out, buffer.dequeue)
+ private def deliver(): Unit = if (isAvailable(out) && buffer.nonEmpty) push[T](out, buffer.dequeue)
- private def bufferElement(receive: (ActorRef, Any)): Unit =
- receive match {
- case (_, element: T @unchecked) =>
- buffer.enqueue(element)
- deliver()
- }
+ private def bufferElement(receive: (ActorRef, Any)): Unit = receive match {
+ case (_, element: T @unchecked) =>
+ buffer.enqueue(element)
+ deliver()
+ }
}
diff --git a/utils/src/main/scala/com.olegych.scastie/util/ProcessActor.scala b/utils/src/main/scala/com.olegych.scastie/util/ProcessActor.scala
index 9b1599837..dbaaf2029 100644
--- a/utils/src/main/scala/com.olegych.scastie/util/ProcessActor.scala
+++ b/utils/src/main/scala/com.olegych.scastie/util/ProcessActor.scala
@@ -3,28 +3,30 @@ package com.olegych.scastie.util
import java.nio.file._
import java.time.Instant
import java.util.concurrent.atomic.AtomicLong
+import scala.concurrent.duration._
import akka.actor.{Actor, ActorRef, Props, Stash}
import akka.contrib.process._
-import akka.stream.scaladsl.{Flow, Framing, Sink, Source}
import akka.stream.{ActorMaterializer, ActorMaterializerSettings, OverflowStrategy, ThrottleMode}
+import akka.stream.scaladsl.{Flow, Framing, Sink, Source}
import akka.util.ByteString
import com.olegych.scastie.api.{ProcessOutput, ProcessOutputType}
import org.slf4j.LoggerFactory
-import scala.concurrent.duration._
-
object ProcessActor {
case class Input(line: String)
case object Shutdown
- def props(command: List[String],
- workingDir: Path = Paths.get(System.getProperty("user.dir")),
- environment: Map[String, String] = Map.empty,
- killOnExit: Boolean = false): Props = {
+ def props(
+ command: List[String],
+ workingDir: Path = Paths.get(System.getProperty("user.dir")),
+ environment: Map[String, String] = Map.empty,
+ killOnExit: Boolean = false
+ ): Props = {
Props(new ProcessActor(command, workingDir, environment, killOnExit))
}
+
}
/*
@@ -34,8 +36,8 @@ object ProcessActor {
*/
class ProcessActor(command: List[String], workingDir: Path, environment: Map[String, String], killOnExit: Boolean)
- extends Actor
- with Stash {
+ extends Actor
+ with Stash {
import ProcessActor._
@@ -49,12 +51,12 @@ class ProcessActor(command: List[String], workingDir: Path, environment: Map[Str
// )
// import NonBlockingProcess._
- private val props =
- BlockingProcess.props(
- command = command,
- workingDir = workingDir.toFile,
- environment = environment
- )
+ private val props = BlockingProcess.props(
+ command = command,
+ workingDir = workingDir.toFile,
+ environment = environment
+ )
+
import BlockingProcess._
private val process = context.actorOf(props, name = "process")
@@ -76,6 +78,7 @@ class ProcessActor(command: List[String], workingDir: Path, environment: Map[Str
)
private val outputId = new AtomicLong(0)
+
override def receive: Receive = {
case Started(pid, stdin, stdout, stderr) =>
println("process started: " + pid)
@@ -90,30 +93,26 @@ class ProcessActor(command: List[String], workingDir: Path, environment: Map[Str
}
)
.throttle(100, 1.second, 100, ThrottleMode.Shaping)
- .runWith(Sink.fold(Instant.now) {
- case (ts, output) =>
- val now = Instant.now
- println(s"> ${output.id.getOrElse(0)} ${now.toEpochMilli - ts.toEpochMilli}ms: ${output.line}")
- context.parent ! output
- now
+ .runWith(Sink.fold(Instant.now) { case (ts, output) =>
+ val now = Instant.now
+ println(s"> ${output.id.getOrElse(0)} ${now.toEpochMilli - ts.toEpochMilli}ms: ${output.line}")
+ context.parent ! output
+ now
})
- val stdin2: Source[ByteString, ActorRef] =
- Source
- .actorRef[Input](Int.MaxValue, OverflowStrategy.fail)
- .map { case Input(line) => ByteString(line + "\n") }
+ val stdin2: Source[ByteString, ActorRef] = Source
+ .actorRef[Input](Int.MaxValue, OverflowStrategy.fail)
+ .map { case Input(line) => ByteString(line + "\n") }
- val ref: ActorRef =
- Flow[ByteString]
- .to(stdin)
- .runWith(stdin2)
+ val ref: ActorRef = Flow[ByteString]
+ .to(stdin)
+ .runWith(stdin2)
context.become(active(ref))
unstashAll()
- case input: Input =>
- stash()
+ case input: Input => stash()
}
private def active(stdin: ActorRef): Receive = {
@@ -121,9 +120,9 @@ class ProcessActor(command: List[String], workingDir: Path, environment: Map[Str
println(s"< ${outputId.incrementAndGet()}: $input")
stdin ! input
- case Exited(exitValue) =>
- if (killOnExit) {
+ case Exited(exitValue) => if (killOnExit) {
throw new Exception("process exited: " + exitValue)
}
}
+
}
diff --git a/utils/src/main/scala/com.olegych.scastie/util/ReconnectingActor.scala b/utils/src/main/scala/com.olegych.scastie/util/ReconnectingActor.scala
index fcc76a811..782aed396 100644
--- a/utils/src/main/scala/com.olegych.scastie/util/ReconnectingActor.scala
+++ b/utils/src/main/scala/com.olegych.scastie/util/ReconnectingActor.scala
@@ -1,12 +1,12 @@
package com.olegych.scastie.util
+import scala.concurrent.duration._
+import scala.concurrent.ExecutionContext.Implicits.global
+
import akka.actor.{Actor, ActorContext, ActorLogging, Cancellable}
import akka.remote.DisassociatedEvent
import com.olegych.scastie.api.ActorConnected
-import scala.concurrent.ExecutionContext.Implicits.global
-import scala.concurrent.duration._
-
case class ReconnectInfo(serverHostname: String, serverAkkaPort: Int, actorHostname: String, actorAkkaPort: Int)
trait ActorReconnecting extends Actor with ActorLogging {
@@ -49,15 +49,13 @@ trait ActorReconnecting extends Actor with ActorLogging {
case ev: DisassociatedEvent => {
println("DisassociatedEvent " + ev)
- val isServerHostname =
- reconnectInfo
- .map(info => ev.remoteAddress.host.contains(info.serverHostname))
- .getOrElse(false)
+ val isServerHostname = reconnectInfo
+ .map(info => ev.remoteAddress.host.contains(info.serverHostname))
+ .getOrElse(false)
- val isServerAkkaPort =
- reconnectInfo
- .map(info => ev.remoteAddress.port.contains(info.serverAkkaPort))
- .getOrElse(false)
+ val isServerAkkaPort = reconnectInfo
+ .map(info => ev.remoteAddress.port.contains(info.serverAkkaPort))
+ .getOrElse(false)
if (isServerHostname && isServerAkkaPort && ev.inbound) {
log.warning("Disconnected from server")
@@ -66,4 +64,5 @@ trait ActorReconnecting extends Actor with ActorLogging {
}
}
}
+
}
diff --git a/utils/src/main/scala/com.olegych.scastie/util/SbtTask.scala b/utils/src/main/scala/com.olegych.scastie/util/SbtTask.scala
index b0dfb5a05..599987157 100644
--- a/utils/src/main/scala/com.olegych.scastie/util/SbtTask.scala
+++ b/utils/src/main/scala/com.olegych.scastie/util/SbtTask.scala
@@ -1,8 +1,7 @@
package com.olegych.scastie.util
-import com.olegych.scastie.api._
-
import akka.actor.ActorRef
+import com.olegych.scastie.api._
case class SbtTask(snippetId: SnippetId, inputs: Inputs, ip: String, login: Option[String], progressActor: ActorRef)
diff --git a/utils/src/main/scala/com.olegych.scastie/util/ScastieFileUtil.scala b/utils/src/main/scala/com.olegych.scastie/util/ScastieFileUtil.scala
index d9fa1902c..01d48a79b 100644
--- a/utils/src/main/scala/com.olegych.scastie/util/ScastieFileUtil.scala
+++ b/utils/src/main/scala/com.olegych.scastie/util/ScastieFileUtil.scala
@@ -1,10 +1,11 @@
package com.olegych.scastie.util
-import java.nio.file._
import java.lang.management.ManagementFactory
import java.nio.charset.StandardCharsets
+import java.nio.file._
object ScastieFileUtil {
+
def slurp(src: Path): Option[String] = {
if (Files.exists(src)) Some(Files.readAllLines(src).toArray.mkString("\n"))
else None
@@ -24,7 +25,7 @@ object ScastieFileUtil {
}
def writeRunningPid(name: String): String = {
- val pid = ManagementFactory.getRuntimeMXBean.getName.split("@").head
+ val pid = ManagementFactory.getRuntimeMXBean.getName.split("@").head
val pidFile = Paths.get(name)
Files.write(pidFile, pid.getBytes(StandardCharsets.UTF_8))
sys.addShutdownHook {
@@ -32,4 +33,5 @@ object ScastieFileUtil {
}
pid
}
+
}
diff --git a/utils/src/test/scala/com.olegych.scastie.util/ProcessActorTest.scala b/utils/src/test/scala/com.olegych.scastie.util/ProcessActorTest.scala
index 0d0f2e9cd..5501ce3cf 100644
--- a/utils/src/test/scala/com.olegych.scastie.util/ProcessActorTest.scala
+++ b/utils/src/test/scala/com.olegych.scastie.util/ProcessActorTest.scala
@@ -2,18 +2,21 @@ package com.olegych.scastie.util
import java.io.File
import java.nio.file.{Files, StandardCopyOption}
+import scala.concurrent.duration._
import akka.actor.{Actor, ActorRef, ActorSystem}
import akka.testkit.{ImplicitSender, TestActorRef, TestKit, TestProbe}
import com.olegych.scastie.api.ProcessOutput
import com.olegych.scastie.api.ProcessOutputType._
import com.olegych.scastie.util.ProcessActor._
-import org.scalatest.BeforeAndAfterAll
import org.scalatest.funsuite.AnyFunSuiteLike
+import org.scalatest.BeforeAndAfterAll
-import scala.concurrent.duration._
-
-class ProcessActorTest() extends TestKit(ActorSystem("ProcessActorTest")) with ImplicitSender with AnyFunSuiteLike with BeforeAndAfterAll {
+class ProcessActorTest()
+ extends TestKit(ActorSystem("ProcessActorTest"))
+ with ImplicitSender
+ with AnyFunSuiteLike
+ with BeforeAndAfterAll {
test("do it") {
(1 to 10).foreach { i =>
@@ -48,15 +51,16 @@ class ProcessActorTest() extends TestKit(ActorSystem("ProcessActorTest")) with I
override def afterAll(): Unit = {
TestKit.shutdownActorSystem(system)
}
+
}
class ProcessReceiver(command: String, probe: ActorRef) extends Actor {
- private val props =
- ProcessActor.props(command = List("bash", "-c", command.replace("\\", "/")), killOnExit = false)
+ private val props = ProcessActor.props(command = List("bash", "-c", command.replace("\\", "/")), killOnExit = false)
private val process = context.actorOf(props, name = "process-receiver")
override def receive: Receive = {
case output: ProcessOutput => probe ! output
case input: Input => process ! input
}
+
}