Skip to content

Commit 92f4359

Browse files
committed
Merge branch 'main' into release
2 parents 06458a7 + 030cb59 commit 92f4359

File tree

4 files changed

+154
-42
lines changed

4 files changed

+154
-42
lines changed

README.md

Lines changed: 83 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,37 +3,105 @@ ijp-scala-console
33

44
IJP Scala Console is simple user interface for executing Scala scripts.
55

6-
[![Actions Status](https://github.com/ij-plugins/ijp-scala-console/workflows/Scala%20CI/badge.svg)](https://github.com/ij-plugins/ijp-scala-console/actions)
7-
[![Maven Central](https://maven-badges.herokuapp.com/maven-central/net.sf.ij-plugins/ijp-scala-console_2.12/badge.svg)](https://maven-badges.herokuapp.com/maven-central/net.sf.ij-plugins/ijp-scala-console_2.12)
8-
[![Scaladoc](https://javadoc.io/badge2/net.sf.ij-plugins/ijp-scala-console_2.12/scaladoc.svg)](https://javadoc.io/doc/net.sf.ij-plugins/ijp-scala-console_2.12)
6+
[![Actions Status](https://github.com/ij-plugins/ijp-scala-console/workflows/Scala%20CI/badge.svg)](https://github.com/ij-plugins/scala-console/actions)
7+
[![Maven Central](https://maven-badges.herokuapp.com/maven-central/net.sf.ij-plugins/scala-console_3/badge.svg)](https://maven-badges.herokuapp.com/maven-central/net.sf.ij-plugins/scala-console_3)
8+
[![Scaladoc](https://javadoc.io/badge2/net.sf.ij-plugins/scala-console_3/scaladoc.svg)](https://javadoc.io/doc/net.sf.ij-plugins/scala-console_3)
9+
10+
**Contents**
11+
12+
<!-- TOC -->
13+
14+
* [ijp-scala-console](#ijp-scala-console)
15+
* [ImageJ Script Examples](#imagej-script-examples)
16+
* [ImageJ Plugin Download](#imagej-plugin-download)
17+
* [Related Projects](#related-projects)
18+
* [License](#license)
19+
20+
<!-- TOC -->
21+
22+
Overview
23+
--------
924

1025
The Scala Console can be run stand-alone, embedded in a desktop application, or [ImageJ] plugin. UI is build with
1126
ScalaFX.
1227

1328
![Screenshot](docs/images/Scala-Console-2_screenshot.png)
1429

30+
ImageJ Plugin Download
31+
----------------------
32+
33+
Binaries can be downloaded from the [releases] page. Extract the binaries to the ImageJ plugins directory. The plugin
34+
will be available through ImageJ menu: `Plugins` > `Scripting` > `Scala Console`.
35+
1536
ImageJ Script Examples
1637
----------------------
1738

18-
Example of an ImageJ script that get a handle to currently selected image and runs the "Median" filter plugin. If there
19-
is no image an error dialog is shown:
39+
### Get Access to Current Image
40+
41+
Assume that you opened an image in ImageJ, for instance, using `File` > `Open Samples` > `Leaf`
42+
43+
The following script will get a handle to the current activa image (`IJ.getImage`), assign it to a value `imp`, and then
44+
print it:
2045

2146
```scala
22-
import ij.IJ._
47+
import ij.IJ
2348

24-
Option(getImage) match {
25-
case Some(imp) => run(imp, "Median...", "radius=4")
26-
case None => noImage()
27-
}
49+
val imp = IJ.getImage
50+
println(imp)
2851
```
2952

30-
Additional example scripts can be found the [examples] directory.
53+
Note: if there is no image open a "No image" dialog will be shown and execution will be aborted
3154

32-
ImageJ Plugin Download
33-
----------------------
55+
Note: that the first thing the script does is to import ImageJ's object `IJ` from package `ij`. Object `IJ` contains
56+
many
57+
frequently used ImageJ methods, like:
58+
59+
* `getImage` - get current image
60+
* `openImage` - load image from a file
61+
* `run` - run a plugin or a command
62+
* `save` - save current image
63+
* `setSlice` - select slice in current image
64+
* `showMessage` - display dialog with a message
65+
66+
Full list can be found here: https://imagej.nih.gov/ij/developer/api/ij/ij/IJ.html
67+
68+
Note: you can use methods contained in `IJ` directly, without prefixing with `IJ`. To do that import a specific method,
69+
for instance, `import ij.IJ.getImage` or all the available methods `import ij.IJ.*`. Here is a shorted version of the
70+
above example:
71+
72+
```scala
73+
import ij.IJ.*
74+
75+
println(getImage)
76+
```
77+
78+
### Run a command "Median..." to process current image
79+
80+
You can execute any menu command using `IJ.run` method and providing command name. In simplest form you only provide command name, it will run on the current open image:
81+
```scala
82+
import ij.IJ.*
83+
84+
run("Median...")
85+
```
86+
The command may open additional dialog asking for options. If you know what options you want to pass you can do that:
87+
```scala
88+
import ij.IJ.*
89+
90+
run("Median...", "radius=4")
91+
```
92+
If you want to control on which image the command runs, you can do that too:
93+
```scala
94+
import ij.IJ.*
95+
96+
val imp = getImage
97+
run(imp, "Median...", "radius=4")
98+
```
99+
The options are listed in a single string using names of fields in the dialog. For boolean values, you use only filed name if value is true (checkbox is checked), you skip the field name of value is false.
100+
101+
Hint: You can use Macro Recorder (`Plugins` > `Macros` > `Record`) to record a command then copy it to your script.
102+
103+
Additional example scripts can be found the [examples] directory.
34104

35-
Binaries can be downloaded from the [releases] page. Extract the binaries to the ImageJ plugins directory. The plugin
36-
install `Plugins` > `Scripting` > `Scala Console`.
37105

38106
Related Projects
39107
----------------

build.sbt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ name := "ijp-scala-console-project"
66
lazy val _scalaVersions = Seq("3.2.2", "2.13.10", "2.12.17")
77
lazy val _scalaVersion = _scalaVersions.head
88

9-
ThisBuild / version := "1.7.1"
9+
ThisBuild / version := "1.7.1.1-SNAPSHOT"
1010
ThisBuild / versionScheme := Some("early-semver")
1111
ThisBuild / organization := "net.sf.ij-plugins"
1212
ThisBuild / sonatypeProfileName := "net.sf.ij-plugins"
@@ -104,7 +104,7 @@ lazy val scala_console = (project in file("scala-console"))
104104
"com.beachape" %% "enumeratum" % "1.7.2",
105105
"org.fxmisc.richtext" % "richtextfx" % "0.11.0",
106106
"org.scala-lang.modules" %% "scala-java8-compat" % "1.0.2",
107-
"org.scalafx" %% "scalafx" % "19.0.0-R30",
107+
"org.scalafx" %% "scalafx" % "20.0.0-R31",
108108
// "org.scalafx" %% "scalafxml-core-sfx8" % "0.5",
109109
"org.scalafx" %% "scalafx-extras" % "0.7.0",
110110
"org.scalatest" %% "scalatest" % "3.2.15" % "test"
@@ -135,7 +135,7 @@ lazy val scala_console_plugins = (project in file("scala-console-plugins"))
135135
description := "Scala Console ImageJ Plugins",
136136
commonSettings,
137137
libraryDependencies ++= Seq(
138-
"net.imagej" % "ij" % "1.54c",
138+
"net.imagej" % "ij" % "1.54d",
139139
"org.scalatest" %% "scalatest" % "3.2.15" % "test"
140140
),
141141
//resolvers += "ImageJ Releases" at "http://maven.imagej.net/content/repositories/releases/"

scala-console/src/main/scala-3/dotty/tools/repl/SCIMain.scala

Lines changed: 61 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,14 @@ package dotty.tools.repl
33
import dotty.tools.dotc.core.StdNames.str
44

55
import java.io.PrintStream
6-
import java.lang.reflect.Method
6+
import java.lang.reflect.{InvocationTargetException, Method}
7+
import scala.annotation.tailrec
78
import scala.util.control.NonFatal
9+
import scala.util.{Failure, Success, Try}
810

911
/** Interprets Scala code, based on `dotty.tools.repl.ScriptEngine` */
1012
class SCIMain(out: PrintStream, loader: ClassLoader) {
13+
import SCIMain.*
1114

1215
private val driver =
1316
new ReplDriver(
@@ -21,32 +24,54 @@ class SCIMain(out: PrintStream, loader: ClassLoader) {
2124
out,
2225
Some(loader)
2326
)
24-
25-
private var state = driver.initialState
2627
private val rendering = new Rendering(Some(getClass.getClassLoader))
28+
private var state = driver.initialState
2729

2830
def bind(tup: (String, Any)): Unit =
2931
state = driver.bind(tup._1, tup._2)(using state)
3032

3133
def interpret(line: String): SCResults = {
32-
val methodOpt: Option[Method] =
33-
try {
34+
// Parse script
35+
val methodTry: Try[Option[Method]] =
36+
Try {
3437
evalToMethod(line)
35-
} catch {
36-
case NonFatal(ex) =>
37-
// ex.printStackTrace()
38-
// ex.printStackTrace(out)
39-
return SCResults.Error
4038
}
4139

42-
val valueOpt: Option[Any] = methodOpt.map(_.invoke(null))
40+
// Execute parsed script
41+
val result: Try[Unit] =
42+
for methodOpt <- methodTry yield {
43+
for method <- methodOpt do
44+
val value = method.invoke(null)
45+
if value != () then
46+
out.println(s"${method.getName}: $value")
47+
}
4348

44-
val value = valueOpt.getOrElse(())
45-
val methodName = methodOpt.fold("")(_.getName)
46-
if (methodOpt.isDefined && valueOpt.isDefined && !valueOpt.contains(())) {
47-
out.println(s"$methodName: $value")
48-
}
49-
SCResults.Success
49+
// Interpret script execution result
50+
result match
51+
case Success(_) =>
52+
// Check if the states indicate that there errors during parsing
53+
if state.context.reporter.hasErrors then
54+
SCResults.Error
55+
else
56+
SCResults.Success
57+
case Failure(ex) =>
58+
ex match {
59+
case ex1 if wasCausedByImageJMacroAbort(ex1) =>
60+
out.println(s"WARNING: Detected ImageJ's \"$IMAGEJ_MACRO_CANCELED\" request. Stopping script execution.")
61+
SCResults.Success
62+
// Attempt to unwrap the exception that may be thrown in the interpreted scripts, skip the wrapping
63+
case e1: InvocationTargetException =>
64+
e1.getCause match {
65+
case ex2: ExceptionInInitializerError =>
66+
Option(ex2.getCause).foreach(_.printStackTrace(out))
67+
case _ =>
68+
// ???
69+
}
70+
SCResults.Error
71+
case _ =>
72+
// ???
73+
SCResults.Error
74+
}
5075
}
5176

5277
private def evalToMethod(script: String): Option[Method] = {
@@ -59,4 +84,23 @@ class SCIMain(out: PrintStream, loader: ClassLoader) {
5984
.find(_.getName == s"${str.REPL_RES_PREFIX}$vid")
6085
}
6186

87+
/**
88+
* Check if exception has a signature of an exception thrown by ImageJ to indicate that macro cancellation.
89+
* @param t exception to test
90+
* @return `true` is the exception matches the ImageJ' macro abort exception.
91+
*/
92+
@tailrec
93+
private final def wasCausedByImageJMacroAbort(t: Throwable): Boolean = {
94+
if t == null then
95+
false
96+
else if t.isInstanceOf[RuntimeException] & t.getMessage == IMAGEJ_MACRO_CANCELED then
97+
true
98+
else
99+
wasCausedByImageJMacroAbort(t.getCause)
100+
}
101+
62102
}
103+
104+
object SCIMain:
105+
private val IMAGEJ_MACRO_CANCELED = "Macro canceled"
106+
end SCIMain

scala-console/src/main/scala/ij_plugins/scala/console/ScalaConsolePaneModel.scala

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -38,15 +38,15 @@ import scalafx.beans.property.{ReadOnlyBooleanProperty, ReadOnlyBooleanWrapper,
3838
*/
3939
class ScalaConsolePaneModel extends ModelFX {
4040

41-
val statusText = new StringProperty("Welcome to Scala Console")
42-
val editor = new Editor()
43-
val outputArea = new OutputArea()
41+
private val listCodeOnRun: Boolean = false
42+
private val _isReady = new ReadOnlyBooleanWrapper(this, "isReady", true)
43+
private val scalaInterpreter = new ScalaInterpreter()
4444

45-
private val _isReady = new ReadOnlyBooleanWrapper(this, "isReady", true)
45+
val statusText = new StringProperty("Welcome to Scala Console")
46+
val editor = new Editor()
47+
val outputArea = new OutputArea()
4648
val isReady: ReadOnlyBooleanProperty = _isReady.readOnlyProperty
4749

48-
private val scalaInterpreter = new ScalaInterpreter()
49-
5050
private val interpreterReactions =
5151
new Subscriber[InterpreterEvent, Publisher[InterpreterEvent]] {
5252
override def notify(pub: Publisher[InterpreterEvent], event: InterpreterEvent): Unit = {
@@ -89,7 +89,7 @@ class ScalaConsolePaneModel extends ModelFX {
8989
val code = if (selection != null && selection.nonEmpty) selection else editor.text
9090

9191
// Show which code will be run
92-
outputArea.model.list(code)
92+
if (listCodeOnRun) outputArea.model.list(code)
9393

9494
// Run the code
9595
scalaInterpreter.run(code)

0 commit comments

Comments
 (0)