scrollback
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -11,3 +11,4 @@ devenv.local.yaml
|
|||||||
# pre-commit
|
# pre-commit
|
||||||
.pre-commit-config.yaml
|
.pre-commit-config.yaml
|
||||||
build
|
build
|
||||||
|
build
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,6 +1,7 @@
|
|||||||
[terminal]
|
[terminal]
|
||||||
columns = 100
|
columns = 100
|
||||||
rows = 30
|
rows = 30
|
||||||
|
max_scrollback = 100000
|
||||||
shell = "/bin/bash"
|
shell = "/bin/bash"
|
||||||
font_family = "JetBrainsMono Nerd Font"
|
font_family = "JetBrainsMono Nerd Font"
|
||||||
font_size = 15
|
font_size = 15
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import java.util.Map;
|
|||||||
public record AppConfig(
|
public record AppConfig(
|
||||||
int columns,
|
int columns,
|
||||||
int rows,
|
int rows,
|
||||||
|
long maxScrollback,
|
||||||
String shell,
|
String shell,
|
||||||
String fontFamily,
|
String fontFamily,
|
||||||
double fontSize,
|
double fontSize,
|
||||||
@@ -51,6 +52,7 @@ public record AppConfig(
|
|||||||
return new AppConfig(
|
return new AppConfig(
|
||||||
intValue(document, "terminal.columns", defaults.columns),
|
intValue(document, "terminal.columns", defaults.columns),
|
||||||
intValue(document, "terminal.rows", defaults.rows),
|
intValue(document, "terminal.rows", defaults.rows),
|
||||||
|
longValue(document, "terminal.max_scrollback", defaults.maxScrollback),
|
||||||
stringValue(document, "terminal.shell", defaults.shell),
|
stringValue(document, "terminal.shell", defaults.shell),
|
||||||
stringValue(document, "terminal.font_family", defaults.fontFamily),
|
stringValue(document, "terminal.font_family", defaults.fontFamily),
|
||||||
doubleValue(document, "terminal.font_size", defaults.fontSize),
|
doubleValue(document, "terminal.font_size", defaults.fontSize),
|
||||||
@@ -69,6 +71,7 @@ public record AppConfig(
|
|||||||
return new AppConfig(
|
return new AppConfig(
|
||||||
100,
|
100,
|
||||||
30,
|
30,
|
||||||
|
100_000,
|
||||||
defaultShell(),
|
defaultShell(),
|
||||||
"JetBrainsMono Nerd Font",
|
"JetBrainsMono Nerd Font",
|
||||||
15.0,
|
15.0,
|
||||||
@@ -93,6 +96,7 @@ public record AppConfig(
|
|||||||
return new AppConfig(
|
return new AppConfig(
|
||||||
columns,
|
columns,
|
||||||
rows,
|
rows,
|
||||||
|
maxScrollback,
|
||||||
shell,
|
shell,
|
||||||
family,
|
family,
|
||||||
size,
|
size,
|
||||||
@@ -154,6 +158,7 @@ public record AppConfig(
|
|||||||
builder.append("[terminal]\n");
|
builder.append("[terminal]\n");
|
||||||
builder.append("columns = ").append(columns).append('\n');
|
builder.append("columns = ").append(columns).append('\n');
|
||||||
builder.append("rows = ").append(rows).append('\n');
|
builder.append("rows = ").append(rows).append('\n');
|
||||||
|
builder.append("max_scrollback = ").append(maxScrollback).append('\n');
|
||||||
builder.append("shell = ").append(quoted(shell)).append('\n');
|
builder.append("shell = ").append(quoted(shell)).append('\n');
|
||||||
builder.append("font_family = ").append(quoted(fontFamily)).append('\n');
|
builder.append("font_family = ").append(quoted(fontFamily)).append('\n');
|
||||||
builder.append("font_size = ").append(trimDouble(fontSize)).append("\n\n");
|
builder.append("font_size = ").append(trimDouble(fontSize)).append("\n\n");
|
||||||
@@ -218,6 +223,18 @@ public record AppConfig(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static long longValue(TomlTable table, String key, long fallback) {
|
||||||
|
TomlPrimitive primitive = primitive(table, key);
|
||||||
|
if (primitive == null) {
|
||||||
|
return fallback;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return primitive.asInteger();
|
||||||
|
} catch (RuntimeException ex) {
|
||||||
|
return fallback;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static double doubleValue(TomlTable table, String key, double fallback) {
|
private static double doubleValue(TomlTable table, String key, double fallback) {
|
||||||
TomlPrimitive primitive = primitive(table, key);
|
TomlPrimitive primitive = primitive(table, key);
|
||||||
if (primitive == null) {
|
if (primitive == null) {
|
||||||
|
|||||||
@@ -30,7 +30,6 @@ public final class Main extends Application {
|
|||||||
StackPane root = new StackPane(terminalView.canvas());
|
StackPane root = new StackPane(terminalView.canvas());
|
||||||
terminalView.canvas().widthProperty().bind(root.widthProperty());
|
terminalView.canvas().widthProperty().bind(root.widthProperty());
|
||||||
terminalView.canvas().heightProperty().bind(root.heightProperty());
|
terminalView.canvas().heightProperty().bind(root.heightProperty());
|
||||||
terminalView.canvas().setOnMousePressed(event -> terminalView.canvas().requestFocus());
|
|
||||||
|
|
||||||
Scene scene = new Scene(root, config.windowWidth(), config.windowHeight());
|
Scene scene = new Scene(root, config.windowWidth(), config.windowHeight());
|
||||||
scene.addEventFilter(KeyEvent.KEY_PRESSED, this::handlePressed);
|
scene.addEventFilter(KeyEvent.KEY_PRESSED, this::handlePressed);
|
||||||
|
|||||||
@@ -5,6 +5,10 @@ import dev.jlibghostty.KittyImageFormat;
|
|||||||
import dev.jlibghostty.KittyImageSnapshot;
|
import dev.jlibghostty.KittyImageSnapshot;
|
||||||
import dev.jlibghostty.KittyPlacement;
|
import dev.jlibghostty.KittyPlacement;
|
||||||
import dev.jlibghostty.KittyRenderInfo;
|
import dev.jlibghostty.KittyRenderInfo;
|
||||||
|
import dev.jlibghostty.KeyModifiers;
|
||||||
|
import dev.jlibghostty.MouseButton;
|
||||||
|
import dev.jlibghostty.MouseEncoderSize;
|
||||||
|
import dev.jlibghostty.MouseInput;
|
||||||
import dev.jlibghostty.RenderCell;
|
import dev.jlibghostty.RenderCell;
|
||||||
import dev.jlibghostty.RenderColor;
|
import dev.jlibghostty.RenderColor;
|
||||||
import dev.jlibghostty.RenderCursorStyle;
|
import dev.jlibghostty.RenderCursorStyle;
|
||||||
@@ -15,6 +19,10 @@ import javafx.scene.canvas.GraphicsContext;
|
|||||||
import javafx.scene.image.Image;
|
import javafx.scene.image.Image;
|
||||||
import javafx.scene.image.PixelFormat;
|
import javafx.scene.image.PixelFormat;
|
||||||
import javafx.scene.image.WritableImage;
|
import javafx.scene.image.WritableImage;
|
||||||
|
import javafx.scene.input.InputEvent;
|
||||||
|
import javafx.scene.input.MouseEvent;
|
||||||
|
import javafx.scene.input.ScrollEvent;
|
||||||
|
import javafx.scene.input.ScrollEvent.VerticalTextScrollUnits;
|
||||||
import javafx.scene.paint.Color;
|
import javafx.scene.paint.Color;
|
||||||
import javafx.scene.text.Font;
|
import javafx.scene.text.Font;
|
||||||
import javafx.scene.text.FontSmoothingType;
|
import javafx.scene.text.FontSmoothingType;
|
||||||
@@ -34,6 +42,8 @@ public final class TerminalCanvasView {
|
|||||||
private final Map<Long, Image> kittyImageCache = new HashMap<>();
|
private final Map<Long, Image> kittyImageCache = new HashMap<>();
|
||||||
private String fontFamily;
|
private String fontFamily;
|
||||||
private double fontSize;
|
private double fontSize;
|
||||||
|
private boolean mouseButtonPressed;
|
||||||
|
private MouseButton pressedButton = MouseButton.UNKNOWN;
|
||||||
|
|
||||||
public TerminalCanvasView(TerminalWorkspace workspace, AppConfig config) {
|
public TerminalCanvasView(TerminalWorkspace workspace, AppConfig config) {
|
||||||
this.workspace = workspace;
|
this.workspace = workspace;
|
||||||
@@ -41,6 +51,11 @@ public final class TerminalCanvasView {
|
|||||||
this.fontFamily = config.fontFamily();
|
this.fontFamily = config.fontFamily();
|
||||||
this.fontSize = config.fontSize();
|
this.fontSize = config.fontSize();
|
||||||
canvas.setFocusTraversable(true);
|
canvas.setFocusTraversable(true);
|
||||||
|
canvas.setOnMousePressed(this::handleMousePressed);
|
||||||
|
canvas.setOnMouseReleased(this::handleMouseReleased);
|
||||||
|
canvas.setOnMouseDragged(this::handleMouseDragged);
|
||||||
|
canvas.setOnMouseMoved(this::handleMouseMoved);
|
||||||
|
canvas.setOnScroll(this::handleScroll);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Canvas canvas() {
|
public Canvas canvas() {
|
||||||
@@ -119,12 +134,187 @@ public final class TerminalCanvasView {
|
|||||||
double lineHeight = Math.max(1.0, text.getLayoutBounds().getHeight());
|
double lineHeight = Math.max(1.0, text.getLayoutBounds().getHeight());
|
||||||
double baselineOffset = -text.getLayoutBounds().getMinY();
|
double baselineOffset = -text.getLayoutBounds().getMinY();
|
||||||
|
|
||||||
Text cell = new Text("M");
|
String sample = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
|
||||||
|
Text cell = new Text(sample);
|
||||||
cell.setFont(font);
|
cell.setFont(font);
|
||||||
double cellWidth = Math.max(1.0, cell.getLayoutBounds().getWidth());
|
double cellWidth = Math.max(1.0, cell.getLayoutBounds().getWidth() / sample.length());
|
||||||
return new FontMetrics(cellWidth, lineHeight, baselineOffset);
|
return new FontMetrics(cellWidth, lineHeight, baselineOffset);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void handleMousePressed(MouseEvent event) {
|
||||||
|
canvas.requestFocus();
|
||||||
|
TerminalPane pane = paneAt(event.getX(), event.getY());
|
||||||
|
if (pane == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
workspace.focus(pane);
|
||||||
|
pressedButton = mouseButton(event);
|
||||||
|
mouseButtonPressed = true;
|
||||||
|
sendMouse(pane, MouseInput.press(pressedButton, eventX(pane, event.getX()), eventY(pane, event.getY()), modifiers(event)), true, event);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleMouseReleased(MouseEvent event) {
|
||||||
|
TerminalPane pane = paneAt(event.getX(), event.getY());
|
||||||
|
if (pane == null) {
|
||||||
|
pane = workspace.activePane();
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseButton button = pressedButton == MouseButton.UNKNOWN ? mouseButton(event) : pressedButton;
|
||||||
|
sendMouse(pane, MouseInput.release(button, eventX(pane, event.getX()), eventY(pane, event.getY()), modifiers(event)), false, event);
|
||||||
|
mouseButtonPressed = false;
|
||||||
|
pressedButton = MouseButton.UNKNOWN;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleMouseDragged(MouseEvent event) {
|
||||||
|
TerminalPane pane = paneAt(event.getX(), event.getY());
|
||||||
|
if (pane == null) {
|
||||||
|
pane = workspace.activePane();
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseButton button = pressedButton == MouseButton.UNKNOWN ? mouseButton(event) : pressedButton;
|
||||||
|
sendMouse(pane, MouseInput.drag(button, eventX(pane, event.getX()), eventY(pane, event.getY()), modifiers(event)), true, event);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleMouseMoved(MouseEvent event) {
|
||||||
|
TerminalPane pane = paneAt(event.getX(), event.getY());
|
||||||
|
if (pane == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
sendMouse(pane, MouseInput.motion(eventX(pane, event.getX()), eventY(pane, event.getY()), modifiers(event)), mouseButtonPressed, event);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleScroll(ScrollEvent event) {
|
||||||
|
TerminalPane pane = paneAt(event.getX(), event.getY());
|
||||||
|
if (pane == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
canvas.requestFocus();
|
||||||
|
workspace.focus(pane);
|
||||||
|
int direction = scrollDirection(event);
|
||||||
|
if (direction == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseButton wheelButton = direction > 0 ? MouseButton.FOUR : MouseButton.FIVE;
|
||||||
|
int rows = scrollRows(event);
|
||||||
|
boolean sent = false;
|
||||||
|
for (int i = 0; i < rows; i++) {
|
||||||
|
sent |= sendMouse(
|
||||||
|
pane,
|
||||||
|
MouseInput.press(wheelButton, eventX(pane, event.getX()), eventY(pane, event.getY()), modifiers(event)),
|
||||||
|
mouseButtonPressed,
|
||||||
|
event
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (!sent) {
|
||||||
|
pane.scrollViewport(direction > 0 ? -rows : rows);
|
||||||
|
event.consume();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean sendMouse(TerminalPane pane, MouseInput input, boolean anyButtonPressed, InputEvent event) {
|
||||||
|
MouseTarget target = mouseTarget(pane);
|
||||||
|
if (target == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean sent = pane.sendMouse(input, target.size(), anyButtonPressed);
|
||||||
|
if (sent) {
|
||||||
|
event.consume();
|
||||||
|
}
|
||||||
|
return sent;
|
||||||
|
}
|
||||||
|
|
||||||
|
private TerminalPane paneAt(double x, double y) {
|
||||||
|
java.util.List<TerminalPane> panes = workspace.panes();
|
||||||
|
for (int i = panes.size() - 1; i >= 0; i--) {
|
||||||
|
TerminalPane pane = panes.get(i);
|
||||||
|
if (x >= pane.x() && x < pane.x() + pane.width() && y >= pane.y() && y < pane.y() + pane.height()) {
|
||||||
|
return pane;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private MouseTarget mouseTarget(TerminalPane pane) {
|
||||||
|
if (pane.width() <= 24.0 || pane.height() <= 24.0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
FontMetrics metrics = measureFontMetrics(Font.font(fontFamily, fontSize));
|
||||||
|
int columns = Math.max(1, (int) ((pane.width() - 24.0) / metrics.cellWidth));
|
||||||
|
int rows = Math.max(1, (int) ((pane.height() - 24.0) / metrics.lineHeight));
|
||||||
|
long cellWidth = Math.max(1L, Math.round(metrics.cellWidth));
|
||||||
|
long cellHeight = Math.max(1L, Math.round(metrics.lineHeight));
|
||||||
|
long screenWidth = Math.max(1L, Math.round(columns * metrics.cellWidth));
|
||||||
|
long screenHeight = Math.max(1L, Math.round(rows * metrics.lineHeight));
|
||||||
|
return new MouseTarget(MouseEncoderSize.of(screenWidth, screenHeight, cellWidth, cellHeight), screenWidth, screenHeight);
|
||||||
|
}
|
||||||
|
|
||||||
|
private double eventX(TerminalPane pane, double canvasX) {
|
||||||
|
MouseTarget target = mouseTarget(pane);
|
||||||
|
if (target == null) {
|
||||||
|
return 0.0;
|
||||||
|
}
|
||||||
|
return clamp(canvasX - pane.x() - 12.0, 0.0, target.screenWidth() - 1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private double eventY(TerminalPane pane, double canvasY) {
|
||||||
|
MouseTarget target = mouseTarget(pane);
|
||||||
|
if (target == null) {
|
||||||
|
return 0.0;
|
||||||
|
}
|
||||||
|
return clamp(canvasY - pane.y() - 12.0, 0.0, target.screenHeight() - 1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static double clamp(double value, double min, double max) {
|
||||||
|
return Math.max(min, Math.min(max, value));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static KeyModifiers modifiers(MouseEvent event) {
|
||||||
|
return KeyModifiers.of(event.isShiftDown(), event.isControlDown(), event.isAltDown(), event.isMetaDown());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static KeyModifiers modifiers(ScrollEvent event) {
|
||||||
|
return KeyModifiers.of(event.isShiftDown(), event.isControlDown(), event.isAltDown(), event.isMetaDown());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int scrollRows(ScrollEvent event) {
|
||||||
|
double rows;
|
||||||
|
if (event.getTextDeltaYUnits() == VerticalTextScrollUnits.LINES && event.getTextDeltaY() != 0.0) {
|
||||||
|
rows = Math.abs(event.getTextDeltaY());
|
||||||
|
} else if (event.getTextDeltaYUnits() == VerticalTextScrollUnits.PAGES && event.getTextDeltaY() != 0.0) {
|
||||||
|
rows = Math.abs(event.getTextDeltaY()) * 24.0;
|
||||||
|
} else if (event.getMultiplierY() > 0.0) {
|
||||||
|
rows = Math.abs(event.getDeltaY()) / event.getMultiplierY();
|
||||||
|
} else {
|
||||||
|
rows = Math.abs(event.getDeltaY()) / 40.0;
|
||||||
|
}
|
||||||
|
return Math.max(1, Math.min(64, (int) Math.ceil(rows)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int scrollDirection(ScrollEvent event) {
|
||||||
|
if (event.getDeltaY() != 0.0) {
|
||||||
|
return event.getDeltaY() > 0.0 ? 1 : -1;
|
||||||
|
}
|
||||||
|
if (event.getTextDeltaYUnits() != VerticalTextScrollUnits.NONE && event.getTextDeltaY() != 0.0) {
|
||||||
|
return event.getTextDeltaY() > 0.0 ? 1 : -1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static MouseButton mouseButton(MouseEvent event) {
|
||||||
|
return switch (event.getButton()) {
|
||||||
|
case PRIMARY -> MouseButton.LEFT;
|
||||||
|
case SECONDARY -> MouseButton.RIGHT;
|
||||||
|
case MIDDLE -> MouseButton.MIDDLE;
|
||||||
|
default -> MouseButton.UNKNOWN;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
private static void drawRow(
|
private static void drawRow(
|
||||||
GraphicsContext gc,
|
GraphicsContext gc,
|
||||||
RenderRow row,
|
RenderRow row,
|
||||||
@@ -139,11 +329,11 @@ public final class TerminalCanvasView {
|
|||||||
double cellTop = top + (row.row() * lineHeight);
|
double cellTop = top + (row.row() * lineHeight);
|
||||||
cell.background().ifPresent(background -> {
|
cell.background().ifPresent(background -> {
|
||||||
gc.setFill(toFxColor(background));
|
gc.setFill(toFxColor(background));
|
||||||
gc.fillRect(x, cellTop, cellWidth, lineHeight);
|
fillCellRect(gc, x, cellTop, cellWidth, lineHeight);
|
||||||
});
|
});
|
||||||
if (cell.selected()) {
|
if (cell.selected()) {
|
||||||
gc.setFill(SELECTED_BACKGROUND);
|
gc.setFill(SELECTED_BACKGROUND);
|
||||||
gc.fillRect(x, cellTop, cellWidth, lineHeight);
|
fillCellRect(gc, x, cellTop, cellWidth, lineHeight);
|
||||||
}
|
}
|
||||||
if (cell.codepoints().length == 0) {
|
if (cell.codepoints().length == 0) {
|
||||||
continue;
|
continue;
|
||||||
@@ -156,6 +346,14 @@ public final class TerminalCanvasView {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void fillCellRect(GraphicsContext gc, double x, double y, double width, double height) {
|
||||||
|
double x1 = Math.floor(x);
|
||||||
|
double y1 = Math.floor(y);
|
||||||
|
double x2 = Math.ceil(x + width);
|
||||||
|
double y2 = Math.ceil(y + height);
|
||||||
|
gc.fillRect(x1, y1, Math.max(1.0, x2 - x1), Math.max(1.0, y2 - y1));
|
||||||
|
}
|
||||||
|
|
||||||
private static Color toFxColor(RenderColor color) {
|
private static Color toFxColor(RenderColor color) {
|
||||||
return Color.rgb(color.red(), color.green(), color.blue());
|
return Color.rgb(color.red(), color.green(), color.blue());
|
||||||
}
|
}
|
||||||
@@ -253,4 +451,7 @@ public final class TerminalCanvasView {
|
|||||||
|
|
||||||
private record FontMetrics(double cellWidth, double lineHeight, double baselineOffset) {
|
private record FontMetrics(double cellWidth, double lineHeight, double baselineOffset) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private record MouseTarget(MouseEncoderSize size, long screenWidth, long screenHeight) {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,12 @@ package com.gregor.jprototerm;
|
|||||||
|
|
||||||
import dev.jlibghostty.Ghostty;
|
import dev.jlibghostty.Ghostty;
|
||||||
import dev.jlibghostty.KittyGraphics;
|
import dev.jlibghostty.KittyGraphics;
|
||||||
|
import dev.jlibghostty.MouseAction;
|
||||||
|
import dev.jlibghostty.MouseEncoder;
|
||||||
|
import dev.jlibghostty.MouseEncoderSize;
|
||||||
|
import dev.jlibghostty.MouseInput;
|
||||||
import dev.jlibghostty.RenderStateSnapshot;
|
import dev.jlibghostty.RenderStateSnapshot;
|
||||||
|
import dev.jlibghostty.ScrollViewport;
|
||||||
import dev.jlibghostty.Terminal;
|
import dev.jlibghostty.Terminal;
|
||||||
import dev.jlibghostty.TerminalOptions;
|
import dev.jlibghostty.TerminalOptions;
|
||||||
import dev.jlibghostty.DeviceAttributes;
|
import dev.jlibghostty.DeviceAttributes;
|
||||||
@@ -12,6 +17,7 @@ import java.util.concurrent.atomic.AtomicReference;
|
|||||||
|
|
||||||
public final class TerminalPane implements AutoCloseable {
|
public final class TerminalPane implements AutoCloseable {
|
||||||
private final Terminal terminal;
|
private final Terminal terminal;
|
||||||
|
private final MouseEncoder mouseEncoder = new MouseEncoder();
|
||||||
private final AtomicReference<RenderStateSnapshot> renderSnapshot = new AtomicReference<>();
|
private final AtomicReference<RenderStateSnapshot> renderSnapshot = new AtomicReference<>();
|
||||||
private ShellSession session;
|
private ShellSession session;
|
||||||
private boolean floating;
|
private boolean floating;
|
||||||
@@ -31,8 +37,8 @@ public final class TerminalPane implements AutoCloseable {
|
|||||||
this.rows = rows;
|
this.rows = rows;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static TerminalPane create(int columns, int rows) {
|
public static TerminalPane create(int columns, int rows, long maxScrollback) {
|
||||||
Terminal terminal = Ghostty.open(TerminalOptions.of(columns, rows));
|
Terminal terminal = Ghostty.open(new TerminalOptions(columns, rows, maxScrollback));
|
||||||
terminal.setDeviceAttributesProvider(DeviceAttributes::xtermCompatible);
|
terminal.setDeviceAttributesProvider(DeviceAttributes::xtermCompatible);
|
||||||
TerminalPane pane = new TerminalPane(terminal, columns, rows);
|
TerminalPane pane = new TerminalPane(terminal, columns, rows);
|
||||||
pane.refresh();
|
pane.refresh();
|
||||||
@@ -65,11 +71,45 @@ public final class TerminalPane implements AutoCloseable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void send(String text) {
|
public void send(String text) {
|
||||||
|
scrollViewportToBottom();
|
||||||
if (session != null) {
|
if (session != null) {
|
||||||
session.send(text);
|
session.send(text);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean sendMouse(MouseInput input, MouseEncoderSize size, boolean anyButtonPressed) {
|
||||||
|
synchronized (terminal) {
|
||||||
|
mouseEncoder.syncFromTerminal(terminal);
|
||||||
|
mouseEncoder.setSize(size);
|
||||||
|
mouseEncoder.setAnyButtonPressed(anyButtonPressed);
|
||||||
|
mouseEncoder.setTrackLastCell(input.action() == MouseAction.MOTION && input.button().isEmpty());
|
||||||
|
|
||||||
|
byte[] encoded = mouseEncoder.encode(input);
|
||||||
|
if (encoded.length == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (session != null) {
|
||||||
|
session.send(encoded);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void scrollViewport(long rows) {
|
||||||
|
synchronized (terminal) {
|
||||||
|
terminal.scrollViewport(ScrollViewport.delta(rows));
|
||||||
|
refresh();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void scrollViewportToBottom() {
|
||||||
|
synchronized (terminal) {
|
||||||
|
terminal.scrollViewport(ScrollViewport.bottom());
|
||||||
|
refresh();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public RenderStateSnapshot renderSnapshot() {
|
public RenderStateSnapshot renderSnapshot() {
|
||||||
return renderSnapshot.get();
|
return renderSnapshot.get();
|
||||||
}
|
}
|
||||||
@@ -150,6 +190,7 @@ public final class TerminalPane implements AutoCloseable {
|
|||||||
session.close();
|
session.close();
|
||||||
session = null;
|
session = null;
|
||||||
}
|
}
|
||||||
|
mouseEncoder.close();
|
||||||
terminal.close();
|
terminal.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,6 +38,13 @@ public final class TerminalWorkspace implements AutoCloseable {
|
|||||||
return activePane() == pane;
|
return activePane() == pane;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void focus(TerminalPane pane) {
|
||||||
|
int index = panes.indexOf(pane);
|
||||||
|
if (index >= 0 && pane.visible()) {
|
||||||
|
activeIndex = index;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void layout(double width, double height) {
|
public void layout(double width, double height) {
|
||||||
List<TerminalPane> tiled = panes.stream()
|
List<TerminalPane> tiled = panes.stream()
|
||||||
.filter(TerminalPane::visible)
|
.filter(TerminalPane::visible)
|
||||||
@@ -235,7 +242,7 @@ public final class TerminalWorkspace implements AutoCloseable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private TerminalPane openPane(boolean floating) {
|
private TerminalPane openPane(boolean floating) {
|
||||||
TerminalPane pane = TerminalPane.create(config.columns(), config.rows());
|
TerminalPane pane = TerminalPane.create(config.columns(), config.rows(), config.maxScrollback());
|
||||||
pane.setFloating(floating);
|
pane.setFloating(floating);
|
||||||
pane.attach(ShellSession.start(config.shell(), pane, config.columns(), config.rows()));
|
pane.attach(ShellSession.start(config.shell(), pane, config.columns(), config.rows()));
|
||||||
return pane;
|
return pane;
|
||||||
|
|||||||
Reference in New Issue
Block a user