Skip to content

Commit 9cae08f

Browse files
Add TerminalSizeCache (#118)
1 parent dd16372 commit 9cae08f

File tree

5 files changed

+164
-3
lines changed

5 files changed

+164
-3
lines changed

README.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,26 @@ int width = size.getWidth();
5050
int height = size.getHeight();
5151
```
5252

53+
If you call this method often, on Unix-es, you might want to use `TerminalSizeCache` instead.
54+
55+
```java
56+
import io.github.alexarchambault.nativeterm.TerminalSize;
57+
import io.github.alexarchambault.nativeterm.TerminalSizeCache;
58+
59+
TerminalSize size = TerminalSizeCache.size();
60+
int width = size.getWidth();
61+
int height = size.getHeight();
62+
```
63+
64+
On Unix-es, `TerminalSizeCache` registers a handler for the WINCH signal, that invalidates
65+
the cached terminal size. That way, the terminal size is queried only when the terminal size
66+
changes, or if it's not cached yet (or the cached value has been invalidated).
67+
If the terminal size didn't change, the cached value is returned, and no unnecessary system call
68+
querying the terminal size is made.
69+
70+
On Windows, this class just calls `NativeTerminal.size()` upon every call to
71+
`TerminalSizeCache.size()`.
72+
5373
## License
5474

5575
All files in this repository, except `NativeImageFeature.java`, can be used either under the

build.sc

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ import mill._
77
import mill.scalalib._
88
import mill.scalalib.publish._
99

10+
import java.util.Locale
11+
12+
import scala.util.Properties
13+
1014
trait WindowsAnsiPublishModule extends PublishModule with Mima {
1115
def pomSettings = PomSettings(
1216
description = artifactName(),
@@ -39,9 +43,39 @@ trait WindowsAnsiPublishModule extends PublishModule with Mima {
3943
else value
4044
}
4145

42-
def javacOptions = super.javacOptions() ++ Seq(
43-
"--release", "8"
44-
)
46+
private def isArm64 =
47+
Option(System.getProperty("os.arch")).map(_.toLowerCase(Locale.ROOT)) match {
48+
case Some("aarch64" | "arm64") => true
49+
case _ => false
50+
}
51+
def javacSystemJvmId = T {
52+
if (Properties.isMac && isArm64) "zulu:8"
53+
else "adoptium:8"
54+
}
55+
def javacSystemJvm = T.source {
56+
val output = os.proc("cs", "java-home", "--jvm", javacSystemJvmId())
57+
.call(cwd = T.workspace)
58+
.out.trim()
59+
val javaHome = os.Path(output)
60+
assert(os.isDir(javaHome))
61+
PathRef(javaHome, quick = true)
62+
}
63+
// adds options equivalent to --release 8 + allowing access to unsupported JDK APIs
64+
// (no more straightforward options to achieve that AFAIK)
65+
def maybeJdk8JavacOpt = T {
66+
val javaHome = javacSystemJvm().path
67+
val rtJar = javaHome / "jre/lib/rt.jar"
68+
val hasModules = os.isDir(javaHome / "jmods")
69+
val hasRtJar = os.isFile(rtJar)
70+
assert(hasModules || hasRtJar)
71+
if (hasModules)
72+
Seq("--system", javaHome.toString)
73+
else
74+
Seq("-source", "8", "-target", "8", "-bootclasspath", rtJar.toString)
75+
}
76+
def javacOptions = T {
77+
super.javacOptions() ++ maybeJdk8JavacOpt()
78+
}
4579

4680
def mimaPreviousVersions: T[Seq[String]] = T.input {
4781
val current = os.proc("git", "describe", "--tags", "--match", "v*")
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package io.github.alexarchambault.nativeterm.internal;
2+
3+
import com.oracle.svm.core.annotate.Substitute;
4+
import com.oracle.svm.core.annotate.TargetClass;
5+
import org.graalvm.nativeimage.Platform;
6+
import org.graalvm.nativeimage.Platforms;
7+
8+
/**
9+
* Voids the SigWinch class when building native images on Windows,
10+
* so that its sun.misc use aren't an issue there.
11+
*/
12+
@TargetClass(className = "io.github.alexarchambault.nativeterm.internal.SigWinch")
13+
@Platforms(Platform.WINDOWS.class)
14+
public final class SigWinchNativeWindows {
15+
16+
@Substitute
17+
public static void addHandler(Runnable runnable) {
18+
// terminal size changes ignored for now
19+
}
20+
21+
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package io.github.alexarchambault.nativeterm;
2+
3+
import io.github.alexarchambault.nativeterm.internal.SigWinch;
4+
5+
/**
6+
* Helper caching the terminal size, and re-querying it only if a SIGWINCH has been received
7+
*
8+
* On Windows, this doesn't cache anything and just re-queries the terminal size upon
9+
* every [[getSize]] call.
10+
*/
11+
public final class TerminalSizeCache {
12+
/**
13+
* Create a new TerminalSizeCache
14+
*
15+
* This sets up a signal handle for the WINCH signal.
16+
*
17+
* As much as possible, only create a single instance of this class,
18+
* and re-use it through your application.
19+
*/
20+
public TerminalSizeCache() {
21+
SigWinch.addHandler(
22+
new Runnable() {
23+
@Override
24+
public void run() {
25+
terminalSize = null;
26+
}
27+
}
28+
);
29+
}
30+
31+
private TerminalSize terminalSize = null;
32+
33+
/**
34+
* Get the terminal size
35+
*
36+
* If the terminal size is in cache, it's returned straightaway.
37+
*
38+
* If the terminal size isn't in cache (not queried yet or recently invalidated by a SIGWINCH),
39+
* it's queried, put in cache, and returned.
40+
*
41+
* @return the terminal size
42+
*/
43+
public TerminalSize getSize() {
44+
if (NativeTerminal.isWindows)
45+
return NativeTerminal.getSize();
46+
47+
TerminalSize terminalSize0 = terminalSize;
48+
if (terminalSize0 == null)
49+
terminalSize0 = update();
50+
return terminalSize0;
51+
}
52+
53+
private TerminalSize update() {
54+
TerminalSize terminalSize0 = NativeTerminal.getSize();
55+
terminalSize = terminalSize0;
56+
return terminalSize0;
57+
}
58+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package io.github.alexarchambault.nativeterm.internal;
2+
3+
import sun.misc.Signal;
4+
import sun.misc.SignalHandler;
5+
6+
/**
7+
* Helper to register handlers of the WINCH signal (terminal size change)
8+
*/
9+
public final class SigWinch {
10+
private SigWinch() {}
11+
12+
/**
13+
* Register a new WINCH handler
14+
* @param runnable A Runnable, run every time a WINCH signal is received
15+
*/
16+
public static void addHandler(Runnable runnable) {
17+
18+
SignalHandler handler = new SignalHandler() {
19+
@Override
20+
public void handle(Signal arg0) {
21+
runnable.run();
22+
}
23+
};
24+
25+
Signal.handle(new Signal("WINCH"), handler);
26+
}
27+
28+
}

0 commit comments

Comments
 (0)