editor adjustments

This commit is contained in:
bQUARKz 2026-04-02 12:52:54 +01:00
parent ba55b8e65f
commit 7bc5c5ba11
Signed by: bquarkz
SSH Key Fingerprint: SHA256:Z7dgqoglWwoK6j6u4QC87OveEq74WOhFN+gitsxtkf8
11 changed files with 339 additions and 107 deletions

View File

@ -78,12 +78,12 @@ public enum I18n {
CODE_EDITOR_NAVIGATOR_DETAIL("codeEditor.navigator.detail"),
CODE_EDITOR_OUTLINE_TITLE("codeEditor.outline.title"),
CODE_EDITOR_OUTLINE_PLACEHOLDER("codeEditor.outline.placeholder"),
CODE_EDITOR_OUTLINE_DIAGNOSTICS("codeEditor.outline.diagnostics"),
CODE_EDITOR_OUTLINE_SYMBOLS("codeEditor.outline.symbols"),
CODE_EDITOR_OUTLINE_EMPTY_DIAGNOSTICS("codeEditor.outline.emptyDiagnostics"),
CODE_EDITOR_OUTLINE_EMPTY_SYMBOLS("codeEditor.outline.emptySymbols"),
CODE_EDITOR_HELPER_TITLE("codeEditor.helper.title"),
CODE_EDITOR_HELPER_PLACEHOLDER("codeEditor.helper.placeholder"),
CODE_EDITOR_HELPER_DIAGNOSTICS("codeEditor.helper.diagnostics"),
CODE_EDITOR_HELPER_EMPTY_DIAGNOSTICS("codeEditor.helper.emptyDiagnostics"),
CODE_EDITOR_TABS_PLACEHOLDER("codeEditor.tabs.placeholder"),
CODE_EDITOR_TABS_OVERFLOW("codeEditor.tabs.overflow"),
CODE_EDITOR_STATUS_BREADCRUMB("codeEditor.status.breadcrumb"),
@ -91,8 +91,6 @@ public enum I18n {
CODE_EDITOR_STATUS_LINE_SEPARATOR("codeEditor.status.lineSeparator"),
CODE_EDITOR_STATUS_INDENTATION("codeEditor.status.indentation"),
CODE_EDITOR_STATUS_LANGUAGE("codeEditor.status.language"),
CODE_EDITOR_STATUS_EDITABLE("codeEditor.status.editable"),
CODE_EDITOR_STATUS_READ_ONLY("codeEditor.status.readOnly"),
CODE_EDITOR_COMMAND_SAVE("codeEditor.command.save"),
CODE_EDITOR_COMMAND_SAVE_ALL("codeEditor.command.saveAll"),
CODE_EDITOR_WARNING_FRONTEND_READ_ONLY("codeEditor.warning.frontendReadOnly"),

View File

@ -2,15 +2,23 @@ package p.studio.workspaces.editor;
import javafx.geometry.Insets;
import javafx.scene.control.Label;
import javafx.scene.control.ScrollPane;
import javafx.scene.layout.VBox;
import p.studio.Container;
import p.studio.controls.WorkspaceDockPane;
import p.studio.lsp.dtos.LspDiagnosticDTO;
import p.studio.utilities.i18n.I18n;
import java.nio.file.Path;
import java.util.List;
public final class EditorHelperPanel extends WorkspaceDockPane {
public static final double COLLAPSED_HEIGHT = 34.0;
public static final double MINIMUM_EXPANDED_HEIGHT = 120.0;
public static final double DEFAULT_EXPANDED_HEIGHT = 180.0;
private final Label summary = new Label();
private final Label diagnosticsTitle = new Label();
private final VBox diagnosticsBox = new VBox(6);
public EditorHelperPanel() {
super(
@ -22,14 +30,67 @@ public final class EditorHelperPanel extends WorkspaceDockPane {
"editor-workspace-helper-pane",
"editor-workspace-helper-panel");
final var placeholder = new Label();
placeholder.textProperty().bind(Container.i18n().bind(I18n.CODE_EDITOR_HELPER_PLACEHOLDER));
placeholder.getStyleClass().add("editor-workspace-placeholder");
placeholder.setWrapText(true);
summary.getStyleClass().addAll("editor-workspace-placeholder", "editor-workspace-helper-summary");
summary.setWrapText(true);
diagnosticsTitle.textProperty().bind(Container.i18n().bind(I18n.CODE_EDITOR_HELPER_DIAGNOSTICS));
diagnosticsTitle.getStyleClass().add("editor-workspace-outline-section-title");
diagnosticsBox.getStyleClass().add("editor-workspace-outline-list");
final var content = new VBox(8, placeholder);
final var content = new VBox(10, summary, diagnosticsTitle, diagnosticsBox);
content.getStyleClass().add("editor-workspace-panel-content");
content.setPadding(new Insets(10, 16, 14, 16));
setDockContent(content);
final var scrollPane = new ScrollPane(content);
scrollPane.setFitToWidth(true);
scrollPane.getStyleClass().add("editor-workspace-outline-scroll");
setDockContent(scrollPane);
showPlaceholder();
}
public void showPlaceholder() {
summary.textProperty().unbind();
summary.textProperty().bind(Container.i18n().bind(I18n.CODE_EDITOR_HELPER_PLACEHOLDER));
diagnosticsBox.getChildren().setAll(placeholderLabel(I18n.CODE_EDITOR_HELPER_EMPTY_DIAGNOSTICS));
}
public void showSemanticReadResult(
final Path documentPath,
final List<LspDiagnosticDTO> diagnostics) {
summary.textProperty().unbind();
summary.setText(documentPath.getFileName() + " • semantic read-only");
rebuildDiagnostics(diagnostics);
}
private void rebuildDiagnostics(final List<LspDiagnosticDTO> diagnostics) {
diagnosticsBox.getChildren().clear();
if (diagnostics.isEmpty()) {
diagnosticsBox.getChildren().add(placeholderLabel(I18n.CODE_EDITOR_HELPER_EMPTY_DIAGNOSTICS));
return;
}
for (final LspDiagnosticDTO diagnostic : diagnostics) {
final var label = new Label(formatDiagnostic(diagnostic));
label.setWrapText(true);
label.getStyleClass().addAll(
"editor-workspace-outline-item",
diagnostic.severity().name().equals("ERROR")
? "editor-workspace-outline-diagnostic-error"
: "editor-workspace-outline-diagnostic-warning");
diagnosticsBox.getChildren().add(label);
}
}
private Label placeholderLabel(final I18n key) {
final var label = new Label();
label.textProperty().bind(Container.i18n().bind(key));
label.setWrapText(true);
label.getStyleClass().addAll("editor-workspace-placeholder", "editor-workspace-outline-item");
return label;
}
private String formatDiagnostic(final LspDiagnosticDTO diagnostic) {
return "%s [%d,%d) %s".formatted(
diagnostic.severity().name(),
diagnostic.range().startOffset(),
diagnostic.range().endOffset(),
diagnostic.message());
}
}

View File

@ -4,7 +4,6 @@ import javafx.geometry.Insets;
import javafx.scene.control.Label;
import javafx.scene.control.ScrollPane;
import javafx.scene.layout.VBox;
import p.studio.lsp.dtos.LspDiagnosticDTO;
import p.studio.lsp.dtos.LspSymbolDTO;
import p.studio.Container;
import p.studio.controls.WorkspaceDockPane;
@ -18,7 +17,6 @@ public final class EditorOutlinePanel extends WorkspaceDockPane {
private static final double MINIMUM_EXPANDED_HEIGHT = 120.0;
private static final double DEFAULT_HEIGHT = 180.0;
private final Label summary = new Label();
private final VBox diagnosticsBox = new VBox(6);
private final VBox symbolsBox = new VBox(6);
public EditorOutlinePanel() {
@ -33,16 +31,12 @@ public final class EditorOutlinePanel extends WorkspaceDockPane {
summary.getStyleClass().addAll("editor-workspace-placeholder", "editor-workspace-outline-summary");
summary.setWrapText(true);
final var diagnosticsTitle = sectionTitle(I18n.CODE_EDITOR_OUTLINE_DIAGNOSTICS);
final var symbolsTitle = sectionTitle(I18n.CODE_EDITOR_OUTLINE_SYMBOLS);
diagnosticsBox.getStyleClass().add("editor-workspace-outline-list");
symbolsBox.getStyleClass().add("editor-workspace-outline-list");
final var content = new VBox(10,
summary,
diagnosticsTitle,
diagnosticsBox,
symbolsTitle,
symbolsBox);
content.getStyleClass().add("editor-workspace-panel-content");
@ -57,38 +51,17 @@ public final class EditorOutlinePanel extends WorkspaceDockPane {
public void showPlaceholder() {
summary.textProperty().unbind();
summary.textProperty().bind(Container.i18n().bind(I18n.CODE_EDITOR_OUTLINE_PLACEHOLDER));
diagnosticsBox.getChildren().setAll(placeholderLabel(I18n.CODE_EDITOR_OUTLINE_EMPTY_DIAGNOSTICS));
symbolsBox.getChildren().setAll(placeholderLabel(I18n.CODE_EDITOR_OUTLINE_EMPTY_SYMBOLS));
}
public void showSemanticReadResult(
final Path documentPath,
final List<LspDiagnosticDTO> diagnostics,
final List<LspSymbolDTO> symbols) {
summary.textProperty().unbind();
summary.setText(documentPath.getFileName() + " • semantic read-only");
rebuildDiagnostics(diagnostics);
rebuildSymbols(symbols);
}
private void rebuildDiagnostics(final List<LspDiagnosticDTO> diagnostics) {
diagnosticsBox.getChildren().clear();
if (diagnostics.isEmpty()) {
diagnosticsBox.getChildren().add(placeholderLabel(I18n.CODE_EDITOR_OUTLINE_EMPTY_DIAGNOSTICS));
return;
}
for (final LspDiagnosticDTO diagnostic : diagnostics) {
final var label = new Label(formatDiagnostic(diagnostic));
label.setWrapText(true);
label.getStyleClass().addAll(
"editor-workspace-outline-item",
diagnostic.severity().name().equals("ERROR")
? "editor-workspace-outline-diagnostic-error"
: "editor-workspace-outline-diagnostic-warning");
diagnosticsBox.getChildren().add(label);
}
}
private void rebuildSymbols(final List<LspSymbolDTO> symbols) {
symbolsBox.getChildren().clear();
if (symbols.isEmpty()) {
@ -125,12 +98,4 @@ public final class EditorOutlinePanel extends WorkspaceDockPane {
label.getStyleClass().addAll("editor-workspace-placeholder", "editor-workspace-outline-item");
return label;
}
private String formatDiagnostic(final LspDiagnosticDTO diagnostic) {
return "%s [%d,%d) %s".formatted(
diagnostic.severity().name(),
diagnostic.range().startOffset(),
diagnostic.range().endOffset(),
diagnostic.message());
}
}

View File

@ -6,6 +6,7 @@ import javafx.scene.control.Label;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.Region;
import javafx.scene.layout.StackPane;
import p.studio.Container;
import p.studio.projects.ProjectReference;
import p.studio.utilities.i18n.I18n;
@ -19,7 +20,7 @@ public final class EditorStatusBar extends HBox {
private final Label lineSeparator = new Label();
private final Label indentation = new Label();
private final Label language = new Label();
private final Label readOnly = new Label();
private final StackPane accessMode = new StackPane();
public EditorStatusBar() {
setAlignment(Pos.CENTER_LEFT);
@ -40,8 +41,7 @@ public final class EditorStatusBar extends HBox {
indentation.getStyleClass().add("editor-workspace-status-chip-indentation");
styleChip(language);
language.getStyleClass().add("editor-workspace-status-chip-language");
styleChip(readOnly);
readOnly.getStyleClass().add("editor-workspace-status-chip-read-only");
accessMode.getStyleClass().addAll("editor-workspace-status-chip", "editor-workspace-status-chip-access-mode");
getChildren().addAll(
breadcrumb,
@ -50,7 +50,7 @@ public final class EditorStatusBar extends HBox {
lineSeparator,
indentation,
language,
readOnly
accessMode
);
}
@ -66,7 +66,7 @@ public final class EditorStatusBar extends HBox {
bindDefault(indentation, I18n.CODE_EDITOR_STATUS_INDENTATION);
setText(language, fileBuffer.typeId());
EditorDocumentPresentationStyles.applyToStatusChip(language, presentation);
showAccessMode(fileBuffer);
showAccessMode(fileBuffer.readOnly());
}
public void showPlaceholder(final EditorDocumentPresentation presentation) {
@ -77,11 +77,8 @@ public final class EditorStatusBar extends HBox {
bindDefault(indentation, I18n.CODE_EDITOR_STATUS_INDENTATION);
bindDefault(language, I18n.CODE_EDITOR_STATUS_LANGUAGE);
EditorDocumentPresentationStyles.applyToStatusChip(language, presentation);
bindDefault(readOnly, I18n.CODE_EDITOR_STATUS_READ_ONLY);
readOnly.getStyleClass().remove("editor-workspace-status-chip-editable");
if (!readOnly.getStyleClass().contains("editor-workspace-status-chip-read-only")) {
readOnly.getStyleClass().add("editor-workspace-status-chip-read-only");
}
accessMode.getChildren().clear();
accessMode.getStyleClass().removeAll("editor-workspace-status-chip-editable", "editor-workspace-status-chip-read-only");
}
private void bindDefault(final Label label, final I18n key) {
@ -175,29 +172,22 @@ public final class EditorStatusBar extends HBox {
setVisibleManaged(lineSeparator, visible);
setVisibleManaged(indentation, visible);
setVisibleManaged(language, visible);
setVisibleManaged(readOnly, visible);
setVisibleManaged(accessMode, visible);
}
private void showAccessMode(final EditorOpenFileBuffer fileBuffer) {
if (fileBuffer.readOnly()) {
bindDefault(readOnly, I18n.CODE_EDITOR_STATUS_READ_ONLY);
readOnly.getStyleClass().remove("editor-workspace-status-chip-editable");
if (!readOnly.getStyleClass().contains("editor-workspace-status-chip-read-only")) {
readOnly.getStyleClass().add("editor-workspace-status-chip-read-only");
}
return;
}
bindDefault(readOnly, I18n.CODE_EDITOR_STATUS_EDITABLE);
readOnly.getStyleClass().remove("editor-workspace-status-chip-read-only");
if (!readOnly.getStyleClass().contains("editor-workspace-status-chip-editable")) {
readOnly.getStyleClass().add("editor-workspace-status-chip-editable");
}
private void showAccessMode(final boolean readOnlyMode) {
accessMode.getChildren().setAll(readOnlyMode
? EditorWorkspaceIcons.lockClosed()
: EditorWorkspaceIcons.lockOpen());
accessMode.getStyleClass().removeAll("editor-workspace-status-chip-editable", "editor-workspace-status-chip-read-only");
accessMode.getStyleClass().add(readOnlyMode
? "editor-workspace-status-chip-read-only"
: "editor-workspace-status-chip-editable");
}
private void setVisibleManaged(final Label label, final boolean visible) {
label.setVisible(visible);
label.setManaged(visible);
private void setVisibleManaged(final Node node, final boolean visible) {
node.setVisible(visible);
node.setManaged(visible);
}
private void styleChip(final Label label) {

View File

@ -0,0 +1,103 @@
package p.studio.workspaces.editor;
import javafx.geometry.Pos;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.Region;
import java.util.List;
import java.util.Objects;
public final class EditorWarningStrip extends HBox {
private final Label message = new Label();
private final Label counter = new Label();
private final Button previousButton = new Button("");
private final Button nextButton = new Button("");
private final Button closeButton = new Button("×");
private List<WarningItem> warnings = List.of();
private int activeIndex;
private boolean dismissed;
public EditorWarningStrip() {
setAlignment(Pos.CENTER_LEFT);
getStyleClass().add("editor-workspace-warning");
message.setWrapText(true);
message.setMaxWidth(Double.MAX_VALUE);
HBox.setHgrow(message, Priority.ALWAYS);
counter.getStyleClass().add("editor-workspace-warning-counter");
previousButton.getStyleClass().add("editor-workspace-warning-button");
nextButton.getStyleClass().add("editor-workspace-warning-button");
closeButton.getStyleClass().add("editor-workspace-warning-button");
previousButton.setFocusTraversable(false);
nextButton.setFocusTraversable(false);
closeButton.setFocusTraversable(false);
previousButton.setOnAction(event -> showIndex(activeIndex - 1));
nextButton.setOnAction(event -> showIndex(activeIndex + 1));
closeButton.setOnAction(event -> dismiss());
final var spacer = new Region();
HBox.setHgrow(spacer, Priority.ALWAYS);
getChildren().addAll(message, spacer, counter, previousButton, nextButton, closeButton);
setVisible(false);
setManaged(false);
}
public void showWarnings(final List<WarningItem> warningItems) {
warnings = List.copyOf(Objects.requireNonNull(warningItems, "warningItems"));
activeIndex = 0;
dismissed = false;
refresh();
}
public void clearWarnings() {
warnings = List.of();
activeIndex = 0;
dismissed = false;
refresh();
}
private void dismiss() {
dismissed = true;
refresh();
}
private void showIndex(final int candidateIndex) {
if (warnings.isEmpty()) {
return;
}
activeIndex = Math.floorMod(candidateIndex, warnings.size());
refresh();
}
private void refresh() {
final boolean visible = !dismissed && !warnings.isEmpty();
setVisible(visible);
setManaged(visible);
if (!visible) {
message.setText("");
counter.setText("");
return;
}
final WarningItem warning = warnings.get(activeIndex);
message.setText(warning.message());
counter.setText(warnings.size() == 1 ? "1/1" : (activeIndex + 1) + "/" + warnings.size());
final boolean canNavigate = warnings.size() > 1;
previousButton.setDisable(!canNavigate);
nextButton.setDisable(!canNavigate);
}
public record WarningItem(String message) {
public WarningItem {
message = Objects.requireNonNull(message, "message");
}
}
}

View File

@ -1,11 +1,12 @@
package p.studio.workspaces.editor;
import javafx.application.Platform;
import javafx.scene.Node;
import javafx.scene.control.Alert;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.SplitPane;
import javafx.scene.layout.*;
import org.fxmisc.flowless.VirtualizedScrollPane;
import org.fxmisc.richtext.CodeArea;
import org.fxmisc.richtext.LineNumberFactory;
import p.studio.lsp.LspService;
@ -27,9 +28,10 @@ import java.util.Objects;
public final class EditorWorkspace extends Workspace {
private final BorderPane root = new BorderPane();
private final CodeArea codeArea = new CodeArea();
private final VirtualizedScrollPane<CodeArea> codeScroller = new VirtualizedScrollPane<>(codeArea);
private final Button saveButton = new Button();
private final Button saveAllButton = new Button();
private final Label readOnlyWarning = new Label();
private final EditorWarningStrip warningStrip = new EditorWarningStrip();
private final EditorProjectNavigatorPanel navigatorPanel = new EditorProjectNavigatorPanel();
private final EditorOutlinePanel outlinePanel = new EditorOutlinePanel();
private final EditorHelperPanel helperPanel = new EditorHelperPanel();
@ -135,9 +137,19 @@ public final class EditorWorkspace extends Workspace {
try {
codeArea.replaceText(fileBuffer.content());
codeArea.setStyleSpans(0, highlighting.styleSpans());
codeArea.moveTo(0);
codeArea.requestFollowCaret();
} finally {
syncingEditor = false;
}
Platform.runLater(() -> {
codeArea.moveTo(0);
codeArea.showParagraphAtTop(0);
codeArea.requestFollowCaret();
if (fileBuffer.editable()) {
codeArea.requestFocus();
}
});
codeArea.setEditable(fileBuffer.editable());
EditorDocumentPresentationStyles.applyToCodeArea(codeArea, presentation);
refreshCommandSurfaces(fileBuffer);
@ -164,25 +176,28 @@ public final class EditorWorkspace extends Workspace {
private void showEditorPlaceholder() {
final EditorDocumentPresentation presentation = presentationRegistry.resolve("text");
final String placeholder = """
// Controlled write wave
// Open a supported project document from the navigator.
""";
applyPresentationStylesheets(presentation);
syncingEditor = true;
try {
codeArea.replaceText(placeholder);
codeArea.setStyleSpans(0, presentation.highlight(placeholder));
codeArea.replaceText("");
codeArea.setStyleSpans(0, presentation.highlight(""));
codeArea.moveTo(0);
codeArea.requestFollowCaret();
} finally {
syncingEditor = false;
}
Platform.runLater(() -> {
codeArea.moveTo(0);
codeArea.showParagraphAtTop(0);
codeArea.requestFollowCaret();
});
codeArea.setEditable(false);
EditorDocumentPresentationStyles.applyToCodeArea(codeArea, presentation);
saveButton.setDisable(true);
saveAllButton.setDisable(true);
readOnlyWarning.setVisible(false);
readOnlyWarning.setManaged(false);
warningStrip.clearWarnings();
outlinePanel.showPlaceholder();
helperPanel.showPlaceholder();
}
private void applyPresentationStylesheets(final EditorDocumentPresentation presentation) {
@ -210,9 +225,13 @@ public final class EditorWorkspace extends Workspace {
}
private VBox buildCenterColumn() {
final var centerColumn = new VBox(12, buildCommandBar(), readOnlyWarning, tabStrip, codeArea);
final var editorSurface = new VBox(0, warningStrip, codeScroller);
editorSurface.getStyleClass().add("editor-workspace-editor-surface");
codeScroller.getStyleClass().add("editor-workspace-code-scroller");
VBox.setVgrow(codeScroller, Priority.ALWAYS);
final var centerColumn = new VBox(12, buildCommandBar(), tabStrip, editorSurface);
centerColumn.getStyleClass().add("editor-workspace-center-column");
VBox.setVgrow(codeArea, Priority.ALWAYS);
VBox.setVgrow(editorSurface, Priority.ALWAYS);
return centerColumn;
}
@ -234,11 +253,7 @@ public final class EditorWorkspace extends Workspace {
}
private void configureWarning() {
readOnlyWarning.textProperty().bind(p.studio.Container.i18n().bind(I18n.CODE_EDITOR_WARNING_FRONTEND_READ_ONLY));
readOnlyWarning.getStyleClass().add("editor-workspace-warning");
readOnlyWarning.setWrapText(true);
readOnlyWarning.setVisible(false);
readOnlyWarning.setManaged(false);
warningStrip.clearWarnings();
}
private HBox buildCommandBar() {
@ -302,9 +317,16 @@ public final class EditorWorkspace extends Workspace {
private void refreshCommandSurfaces(final EditorOpenFileBuffer fileBuffer) {
saveButton.setDisable(!fileBuffer.saveEnabled());
saveAllButton.setDisable(!openFileSession.hasDirtyEditableFiles());
final boolean showWarning = fileBuffer.frontendDocument() && fileBuffer.readOnly();
readOnlyWarning.setVisible(showWarning);
readOnlyWarning.setManaged(showWarning);
final List<EditorWarningStrip.WarningItem> warnings = new ArrayList<>();
if (fileBuffer.frontendDocument() && fileBuffer.readOnly()) {
warnings.add(new EditorWarningStrip.WarningItem(
p.studio.Container.i18n().text(I18n.CODE_EDITOR_WARNING_FRONTEND_READ_ONLY)));
}
if (warnings.isEmpty()) {
warningStrip.clearWarnings();
return;
}
warningStrip.showWarnings(warnings);
}
private EditorOpenFileBuffer bufferFrom(final VfsDocumentOpenResult.VfsTextDocument textDocument) {
@ -324,11 +346,10 @@ public final class EditorWorkspace extends Workspace {
final LspAnalyzeDocumentResult analysis) {
if (!fileBuffer.frontendDocument() || analysis == null) {
outlinePanel.showPlaceholder();
helperPanel.showPlaceholder();
return;
}
outlinePanel.showSemanticReadResult(
fileBuffer.path(),
analysis.diagnostics(),
analysis.documentSymbols());
outlinePanel.showSemanticReadResult(fileBuffer.path(), analysis.documentSymbols());
helperPanel.showSemanticReadResult(fileBuffer.path(), analysis.diagnostics());
}
}

View File

@ -4,7 +4,14 @@ import javafx.scene.Node;
import javafx.scene.layout.StackPane;
import javafx.scene.shape.SVGPath;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public final class EditorWorkspaceIcons {
private static final Pattern SVG_PATH_PATTERN = Pattern.compile("<path[^>]*\\sd=\"([^\"]+)\"");
private static final String FOLDER_PATH = "M1.5 4.5A1.5 1.5 0 0 1 3 3h3.2l1.1 1.4H13A1.5 1.5 0 0 1 14.5 5.9V12A1.5 1.5 0 0 1 13 13.5H3A1.5 1.5 0 0 1 1.5 12z";
private static final String FILE_PATH = "M4 1.5h5l3 3V13A1.5 1.5 0 0 1 10.5 14.5h-6A1.5 1.5 0 0 1 3 13V3A1.5 1.5 0 0 1 4.5 1.5zm4.7 1.2V5h2.3z";
private static final String COG_PATH = "M9.405 1.05c-.413-1.4-2.397-1.4-2.81 0l-.1.34a1.464 1.464 0 0 1-2.105.872l-.31-.17c-1.24-.68-2.64.72-1.96 1.96l.17.31c.446.816.023 1.84-.872 2.105l-.34.1c-1.4.413-1.4 2.397 0 2.81l.34.1c.895.265 1.318 1.289.872 2.105l-.17.31c-.68 1.24.72 2.64 1.96 1.96l.31-.17a1.464 1.464 0 0 1 2.105.872l.1.34c.413 1.4 2.397 1.4 2.81 0l.1-.34a1.464 1.464 0 0 1 2.105-.872l.31.17c1.24.68 2.64-.72 1.96-1.96l-.17-.31a1.464 1.464 0 0 1 .872-2.105l.34-.1c1.4-.413 1.4-2.397 0-2.81l-.34-.1a1.464 1.464 0 0 1-.872-2.105l.17-.31c.68-1.24-.72-2.64-1.96-1.96l-.31.17a1.464 1.464 0 0 1-2.105-.872z";
@ -42,6 +49,14 @@ public final class EditorWorkspaceIcons {
return icon(TARGET_PATH, "editor-workspace-icon-target");
}
public static Node lockClosed() {
return icon(iconPathFromResource("/icons/editor/lock-closed.svg"), "editor-workspace-icon-lock-closed");
}
public static Node lockOpen() {
return icon(iconPathFromResource("/icons/editor/lock-open.svg"), "editor-workspace-icon-lock-open");
}
private static Node icon(final String path, final String toneClass) {
final var shape = new SVGPath();
shape.setContent(path);
@ -51,4 +66,20 @@ public final class EditorWorkspaceIcons {
icon.getStyleClass().add("editor-workspace-icon");
return icon;
}
private static String iconPathFromResource(final String resourcePath) {
try (InputStream inputStream = EditorWorkspaceIcons.class.getResourceAsStream(resourcePath)) {
if (inputStream == null) {
throw new IllegalStateException("Missing icon resource: " + resourcePath);
}
final String svg = new String(inputStream.readAllBytes(), StandardCharsets.UTF_8);
final Matcher matcher = SVG_PATH_PATTERN.matcher(svg);
if (!matcher.find()) {
throw new IllegalStateException("Missing <path d=\"...\"> in icon resource: " + resourcePath);
}
return matcher.group(1);
} catch (IOException exception) {
throw new IllegalStateException("Unable to read icon resource: " + resourcePath, exception);
}
}
}

View File

@ -68,13 +68,13 @@ codeEditor.navigator.revealActive=Reveal active file
codeEditor.navigator.placeholder=Project navigation lands in the next implementation slice.
codeEditor.navigator.detail=This first shell reserves the full navigator surface, its refresh action, and the left-column composition without wiring project-tree data yet.
codeEditor.outline.title=Outline
codeEditor.outline.placeholder=Open a frontend document to inspect read-only diagnostics and symbols.
codeEditor.outline.diagnostics=Diagnostics
codeEditor.outline.placeholder=Open a frontend document to inspect semantic symbols.
codeEditor.outline.symbols=Symbols
codeEditor.outline.emptyDiagnostics=No diagnostics for the active frontend document.
codeEditor.outline.emptySymbols=No semantic symbols are currently available for the active frontend document.
codeEditor.helper.title=Editor Helper
codeEditor.helper.placeholder=This region is intentionally passive in the first read-only wave.
codeEditor.helper.placeholder=Open a frontend document to inspect semantic diagnostics.
codeEditor.helper.diagnostics=Diagnostics
codeEditor.helper.emptyDiagnostics=No diagnostics for the active frontend document.
codeEditor.tabs.placeholder=no-file-open.txt
codeEditor.tabs.overflow=More
codeEditor.status.breadcrumb=proj > src > file.pbs
@ -82,8 +82,6 @@ codeEditor.status.position=L:C
codeEditor.status.lineSeparator=LF
codeEditor.status.indentation=Spaces: 4
codeEditor.status.language=Text
codeEditor.status.editable=Editable
codeEditor.status.readOnly=Read-only
codeEditor.command.save=Save
codeEditor.command.saveAll=Save All
codeEditor.warning.frontendReadOnly=This frontend file is read-only in this wave. It cannot be edited or saved yet.

View File

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
<path d="M5 6V4.75a3 3 0 1 1 6 0V6h.75A1.25 1.25 0 0 1 13 7.25v5.5A1.25 1.25 0 0 1 11.75 14h-7.5A1.25 1.25 0 0 1 3 12.75v-5.5A1.25 1.25 0 0 1 4.25 6zm1.5 0h3V4.75a1.5 1.5 0 1 0-3 0z"/>
</svg>

After

Width:  |  Height:  |  Size: 255 B

View File

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
<path d="M3.2 10.667c0-4.137.11-4.267 3.609-4.267 2.758 0 3.746-.523 4.189-2.218.766-2.928 5.375-2.44 5.834.618.32 2.131.318 2.131-1.365.005-1.445-1.825-1.826-1.901-2.675-.533-.618.996-.603 1.728.042 1.95.568.196 1.032 2.236 1.032 4.534v4.178H8.533H3.2z"/>
</svg>

After

Width:  |  Height:  |  Size: 327 B

View File

@ -506,6 +506,14 @@
-fx-fill: #eff5fb;
}
.editor-workspace-icon-lock-closed {
-fx-fill: #f6d78f;
}
.editor-workspace-icon-lock-open {
-fx-fill: #bde7c7;
}
.editor-workspace-outline-panel {
-fx-min-height: 0;
}
@ -686,6 +694,16 @@
-fx-fill: #f2f6fb;
}
.editor-workspace-code-area .caret {
-fx-stroke: #ffd27a;
}
.editor-workspace-editor-surface {
-fx-background-color: #171c22;
-fx-border-color: #2a313c;
-fx-border-width: 1;
}
.editor-workspace-code-area-type-text .text {
-fx-fill: #eef3f8;
}
@ -707,13 +725,43 @@
}
.editor-workspace-warning {
-fx-alignment: center-left;
-fx-spacing: 8;
-fx-padding: 8 12 8 12;
-fx-background-color: #3b2a10;
-fx-border-color: #8f6730;
-fx-border-width: 0 0 1 0;
-fx-text-fill: #f7ddb0;
-fx-font-size: 12px;
}
.editor-workspace-warning-counter {
-fx-text-fill: #f3d8a7;
-fx-font-size: 11px;
-fx-font-weight: 700;
}
.editor-workspace-warning-button {
-fx-min-width: 24;
-fx-pref-width: 24;
-fx-max-width: 24;
-fx-min-height: 24;
-fx-pref-height: 24;
-fx-max-height: 24;
-fx-padding: 0;
-fx-background-color: #4b3413;
-fx-border-color: #9a7440;
-fx-text-fill: #f7ddb0;
-fx-font-size: 11px;
-fx-background-radius: 4;
-fx-border-radius: 4;
}
.editor-workspace-warning-button:hover {
-fx-background-color: #5a4018;
-fx-border-color: #b38749;
}
.workspace-dock-pane {
-fx-collapsible: true;
-fx-background-color: transparent;
@ -753,6 +801,10 @@
-fx-min-height: 0;
}
.editor-workspace-helper-summary {
-fx-text-fill: #dce6f0;
}
.workspace-dock-pane-collapsed {
-fx-background-insets: 0;
}
@ -858,10 +910,17 @@
-fx-max-width: 72;
}
.editor-workspace-status-chip-access-mode {
-fx-min-width: 36;
-fx-pref-width: 36;
-fx-max-width: 36;
-fx-padding: 0;
}
.editor-workspace-status-chip-read-only {
-fx-min-width: 88;
-fx-pref-width: 88;
-fx-max-width: 88;
-fx-background-color: #3b2a10;
-fx-border-color: #8f6730;
-fx-text-fill: #f7ddb0;
}
.editor-workspace-status-chip-editable {