From dfb553d9e870f25eb16a801823ccd40af8707782 Mon Sep 17 00:00:00 2001 From: Daniel Date: Sat, 31 Jan 2026 16:25:17 +0100 Subject: [PATCH 1/5] initial throw of git blame feature --- build.gradle | 3 +- .../jsoneditor/model/ReadableModel.java | 16 ++ .../jsoneditor/model/git/GitBlameInfo.java | 89 +++++++++ .../model/git/GitBlameIntegration.java | 101 ++++++++++ .../jsoneditor/model/git/GitBlameService.java | 173 ++++++++++++++++++ .../model/git/JsonPathToLineMapper.java | 81 ++++++++ .../jsoneditor/model/impl/ModelImpl.java | 20 ++ .../reference/ReferenceToObjectInstance.java | 5 +- .../buttons/WindowGitBlameToggleButton.java | 28 +++ .../editorwindow/JsonEditorEditorWindow.java | 25 ++- .../components/JsonEditorNamebar.java | 12 +- .../TableViewWithCompactNamebar.java | 5 + .../components/tableview/EditorTableView.java | 10 + .../tableview/impl/EditorTableViewImpl.java | 28 ++- .../tableview/impl/TableColumnFactory.java | 8 + .../impl/columns/GitBlameColumn.java | 110 +++++++++++ .../darkmode/outline_git_blame_white_24dp.png | Bin 0 -> 630 bytes src/main/resources/version.properties | 2 +- 18 files changed, 709 insertions(+), 7 deletions(-) create mode 100644 src/main/java/com/daniel/jsoneditor/model/git/GitBlameInfo.java create mode 100644 src/main/java/com/daniel/jsoneditor/model/git/GitBlameIntegration.java create mode 100644 src/main/java/com/daniel/jsoneditor/model/git/GitBlameService.java create mode 100644 src/main/java/com/daniel/jsoneditor/model/git/JsonPathToLineMapper.java create mode 100644 src/main/java/com/daniel/jsoneditor/view/impl/jfx/buttons/WindowGitBlameToggleButton.java create mode 100644 src/main/java/com/daniel/jsoneditor/view/impl/jfx/impl/scenes/impl/editor/components/editorwindow/components/tableview/impl/columns/GitBlameColumn.java create mode 100644 src/main/resources/icons/material/darkmode/outline_git_blame_white_24dp.png diff --git a/build.gradle b/build.gradle index d094941..41cbb29 100644 --- a/build.gradle +++ b/build.gradle @@ -24,7 +24,8 @@ javafx { dependencies { implementation 'com.networknt:json-schema-validator:1.0.87' - implementation 'ch.qos.logback:logback-classic:1.4.14' + implementation 'ch.qos.logback:logback-classic:1.5.17' + implementation 'org.eclipse.jgit:org.eclipse.jgit:6.10.0.202406032230-r' implementation name: 'smartgraph-2.0.0' //local jar that has been recompiled to run on java 11 testImplementation 'org.junit.jupiter:junit-jupiter-api:5.10.0' testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.10.0' diff --git a/src/main/java/com/daniel/jsoneditor/model/ReadableModel.java b/src/main/java/com/daniel/jsoneditor/model/ReadableModel.java index d70878c..ec41ebf 100644 --- a/src/main/java/com/daniel/jsoneditor/model/ReadableModel.java +++ b/src/main/java/com/daniel/jsoneditor/model/ReadableModel.java @@ -1,6 +1,7 @@ package com.daniel.jsoneditor.model; import com.daniel.jsoneditor.model.commands.CommandFactory; +import com.daniel.jsoneditor.model.git.GitBlameInfo; import com.daniel.jsoneditor.model.impl.graph.NodeGraph; import com.daniel.jsoneditor.model.json.schema.reference.ReferenceToObject; import com.daniel.jsoneditor.model.json.schema.reference.ReferenceToObjectInstance; @@ -106,4 +107,19 @@ public interface ReadableModel extends ReadableState String getIdentifier(String pathOfParent, JsonNode child); + /** + * Get git blame information for a JSON path. + * + * @param jsonPath JSON path (e.g., "/root/child/property") + * @return blame info or null if not available or not in git repo + */ + GitBlameInfo getBlameForPath(String jsonPath); + + /** + * Check if git blame is available for the current file. + * + * @return true if file is in a git repository + */ + boolean isGitBlameAvailable(); + } diff --git a/src/main/java/com/daniel/jsoneditor/model/git/GitBlameInfo.java b/src/main/java/com/daniel/jsoneditor/model/git/GitBlameInfo.java new file mode 100644 index 0000000..67da8b3 --- /dev/null +++ b/src/main/java/com/daniel/jsoneditor/model/git/GitBlameInfo.java @@ -0,0 +1,89 @@ +package com.daniel.jsoneditor.model.git; + +import java.time.Instant; + +public class GitBlameInfo +{ + private final String authorName; + private final String authorEmail; + private final String commitHash; + private final Instant commitTime; + private final String commitMessage; + + public GitBlameInfo(String authorName, String authorEmail, String commitHash, Instant commitTime, String commitMessage) + { + this.authorName = authorName; + this.authorEmail = authorEmail; + this.commitHash = commitHash; + this.commitTime = commitTime; + this.commitMessage = commitMessage; + } + + public String getAuthorName() + { + return authorName; + } + + public String getAuthorEmail() + { + return authorEmail; + } + + public String getCommitHash() + { + return commitHash; + } + + public String getShortCommitHash() + { + return commitHash != null && commitHash.length() > 7 ? commitHash.substring(0, 7) : commitHash; + } + + public Instant getCommitTime() + { + return commitTime; + } + + public String getCommitMessage() + { + return commitMessage; + } + + public String getShortCommitMessage() + { + if (commitMessage == null) return ""; + final int newlineIndex = commitMessage.indexOf('\n'); + return newlineIndex > 0 ? commitMessage.substring(0, newlineIndex) : commitMessage; + } + + /** + * Get a color derived from the commit hash for visual distinction. + * Returns a web color string like "#A3B5C7" + */ + public String getCommitColor() + { + if (commitHash == null || commitHash.isEmpty()) + { + return "#808080"; + } + + final int hash = commitHash.hashCode(); + final int r = (hash & 0xFF0000) >> 16; + final int g = (hash & 0x00FF00) >> 8; + final int b = hash & 0x0000FF; + + final int minBrightness = 80; + final int maxBrightness = 200; + final int adjustedR = minBrightness + (r * (maxBrightness - minBrightness) / 255); + final int adjustedG = minBrightness + (g * (maxBrightness - minBrightness) / 255); + final int adjustedB = minBrightness + (b * (maxBrightness - minBrightness) / 255); + + return String.format("#%02X%02X%02X", adjustedR, adjustedG, adjustedB); + } + + @Override + public String toString() + { + return String.format("%s (%s)", authorName, getShortCommitHash()); + } +} diff --git a/src/main/java/com/daniel/jsoneditor/model/git/GitBlameIntegration.java b/src/main/java/com/daniel/jsoneditor/model/git/GitBlameIntegration.java new file mode 100644 index 0000000..c409231 --- /dev/null +++ b/src/main/java/com/daniel/jsoneditor/model/git/GitBlameIntegration.java @@ -0,0 +1,101 @@ +package com.daniel.jsoneditor.model.git; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.nio.file.Path; + +/** + * External tool integration for git blame functionality. + * Combines GitBlameService and JsonPathToLineMapper to provide + * blame information for JSON paths. + */ +public class GitBlameIntegration +{ + private static final Logger logger = LoggerFactory.getLogger(GitBlameIntegration.class); + + private final GitBlameService blameService = new GitBlameService(); + private final JsonPathToLineMapper lineMapper = new JsonPathToLineMapper(); + + private String relativeFilePath; + private boolean initialized = false; + + /** + * Initialize with a JSON file. + * Checks if file is in a git repository and builds path-to-line mapping. + * + * @param jsonFilePath absolute path to JSON file + * @return true if successfully initialized + */ + public boolean initialize(Path jsonFilePath) + { + close(); + + if (!blameService.initialize(jsonFilePath)) + { + logger.debug("Git blame not available for: {}", jsonFilePath); + return false; + } + + relativeFilePath = blameService.getRelativePath(jsonFilePath); + if (relativeFilePath == null) + { + logger.warn("Could not determine relative path for: {}", jsonFilePath); + close(); + return false; + } + + lineMapper.buildMapping(jsonFilePath); + initialized = true; + logger.info("Git blame initialized for: {} ({})", jsonFilePath, relativeFilePath); + return true; + } + + /** + * Get blame information for a JSON path. + * + * @param jsonPath JSON path (e.g., "/root/child/property") + * @return blame info or null if not available + */ + public GitBlameInfo getBlameForPath(String jsonPath) + { + if (!initialized) + { + return null; + } + + final int lineNumber = lineMapper.getLineForPathOrParent(jsonPath); + if (lineNumber < 0) + { + logger.debug("No line number found for path: {}", jsonPath); + return null; + } + + return blameService.getBlameForLine(relativeFilePath, lineNumber); + } + + public boolean isAvailable() + { + return initialized; + } + + /** + * Refresh the line mapping after file changes. + * Should be called after saving the file. + */ + public void refresh(Path jsonFilePath) + { + if (initialized) + { + lineMapper.buildMapping(jsonFilePath); + blameService.clearCache(); + } + } + + public void close() + { + blameService.close(); + initialized = false; + relativeFilePath = null; + } +} diff --git a/src/main/java/com/daniel/jsoneditor/model/git/GitBlameService.java b/src/main/java/com/daniel/jsoneditor/model/git/GitBlameService.java new file mode 100644 index 0000000..6387739 --- /dev/null +++ b/src/main/java/com/daniel/jsoneditor/model/git/GitBlameService.java @@ -0,0 +1,173 @@ +package com.daniel.jsoneditor.model.git; + +import org.eclipse.jgit.api.Git; +import org.eclipse.jgit.api.errors.GitAPIException; +import org.eclipse.jgit.blame.BlameResult; +import org.eclipse.jgit.lib.PersonIdent; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.revwalk.RevCommit; +import org.eclipse.jgit.storage.file.FileRepositoryBuilder; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Path; +import java.time.Instant; +import java.util.HashMap; +import java.util.Map; + +/** + * Service for retrieving git blame information for JSON file lines. + * Provides information about who last modified each line in a file. + */ +public class GitBlameService +{ + private static final Logger logger = LoggerFactory.getLogger(GitBlameService.class); + + private Repository repository; + private Git git; + private final Map blameCache = new HashMap<>(); + + /** + * Initialize the service with a JSON file path. + * Attempts to find the git repository containing the file. + * + * @param jsonFilePath path to the JSON file + * @return true if git repository was found and initialized + */ + public boolean initialize(Path jsonFilePath) + { + close(); + + try + { + final File file = jsonFilePath.toFile(); + final FileRepositoryBuilder builder = new FileRepositoryBuilder(); + repository = builder + .findGitDir(file) + .setMustExist(true) + .build(); + + git = new Git(repository); + logger.info("Git repository found: {}", repository.getDirectory()); + return true; + } + catch (IOException e) + { + logger.debug("No git repository found for file: {}", jsonFilePath); + return false; + } + } + + /** + * Get blame information for a specific line in a file. + * + * @param relativeFilePath path relative to git repository root + * @param lineNumber 0-based line number + * @return blame info or null if not available + */ + public GitBlameInfo getBlameForLine(String relativeFilePath, int lineNumber) + { + if (git == null || repository == null) + { + return null; + } + + try + { + final BlameResult blameResult = getOrComputeBlame(relativeFilePath); + if (blameResult == null) + { + return null; + } + + final RevCommit commit = blameResult.getSourceCommit(lineNumber); + if (commit == null) + { + return null; + } + + final PersonIdent author = blameResult.getSourceAuthor(lineNumber); + final Instant commitTime = Instant.ofEpochSecond(commit.getCommitTime()); + + return new GitBlameInfo( + author.getName(), + author.getEmailAddress(), + commit.getName(), + commitTime, + commit.getFullMessage() + ); + } + catch (Exception e) + { + logger.error("Error getting blame info for {}:{}", relativeFilePath, lineNumber, e); + return null; + } + } + + private BlameResult getOrComputeBlame(String relativeFilePath) throws GitAPIException + { + if (!blameCache.containsKey(relativeFilePath)) + { + logger.debug("Computing blame for file: {}", relativeFilePath); + final BlameResult result = git.blame() + .setFilePath(relativeFilePath) + .call(); + blameCache.put(relativeFilePath, result); + } + return blameCache.get(relativeFilePath); + } + + /** + * Get the relative path of a file from the repository root. + * + * @param absolutePath absolute file path + * @return relative path or null if file is not in repository + */ + public String getRelativePath(Path absolutePath) + { + if (repository == null) + { + return null; + } + + final Path workTree = repository.getWorkTree().toPath(); + if (absolutePath.startsWith(workTree)) + { + return workTree.relativize(absolutePath).toString().replace('\\', '/'); + } + return null; + } + + public boolean isInitialized() + { + return git != null && repository != null; + } + + /** + * Clear the blame cache. Should be called when file content changes. + */ + public void clearCache() + { + blameCache.clear(); + } + + /** + * Close the git repository and release resources. + */ + public void close() + { + blameCache.clear(); + if (git != null) + { + git.close(); + git = null; + } + if (repository != null) + { + repository.close(); + repository = null; + } + } +} diff --git a/src/main/java/com/daniel/jsoneditor/model/git/JsonPathToLineMapper.java b/src/main/java/com/daniel/jsoneditor/model/git/JsonPathToLineMapper.java new file mode 100644 index 0000000..f1634e4 --- /dev/null +++ b/src/main/java/com/daniel/jsoneditor/model/git/JsonPathToLineMapper.java @@ -0,0 +1,81 @@ +package com.daniel.jsoneditor.model.git; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; + +/** + * Simple mapper that finds line numbers for JSON property keys. + * For MVP: searches for the last path segment as a JSON key. + */ +public class JsonPathToLineMapper +{ + private static final Logger logger = LoggerFactory.getLogger(JsonPathToLineMapper.class); + + private List lines; + + public void buildMapping(Path jsonFilePath) + { + try + { + lines = Files.readAllLines(jsonFilePath); + } + catch (IOException e) + { + logger.error("Failed to read JSON file: {}", jsonFilePath, e); + lines = null; + } + } + + /** + * Find line number for a JSON path by searching for the last path segment. + * + * @param fullPath JSON path like "/root/child/property" + * @return 0-based line number or -1 if not found + */ + public int getLineForPathOrParent(String fullPath) + { + if (lines == null || fullPath == null || fullPath.isEmpty()) + { + return -1; + } + + final String lastSegment = getLastSegment(fullPath); + if (lastSegment == null) + { + return 0; + } + + final String searchPattern = "\"" + lastSegment + "\""; + + for (int i = 0; i < lines.size(); i++) + { + if (lines.get(i).contains(searchPattern)) + { + return i; + } + } + + return -1; + } + + private String getLastSegment(String path) + { + if (path == null || path.isEmpty()) + { + return null; + } + + final int lastSlash = path.lastIndexOf('/'); + if (lastSlash < 0 || lastSlash == path.length() - 1) + { + return null; + } + + return path.substring(lastSlash + 1); + } +} diff --git a/src/main/java/com/daniel/jsoneditor/model/impl/ModelImpl.java b/src/main/java/com/daniel/jsoneditor/model/impl/ModelImpl.java index c7ee2c3..2d2a1da 100644 --- a/src/main/java/com/daniel/jsoneditor/model/impl/ModelImpl.java +++ b/src/main/java/com/daniel/jsoneditor/model/impl/ModelImpl.java @@ -2,6 +2,8 @@ import com.daniel.jsoneditor.model.WritableModelInternal; import com.daniel.jsoneditor.model.commands.CommandFactory; +import com.daniel.jsoneditor.model.git.GitBlameIntegration; +import com.daniel.jsoneditor.model.git.GitBlameInfo; import com.daniel.jsoneditor.model.impl.graph.NodeGraph; import com.daniel.jsoneditor.model.impl.graph.NodeGraphCreator; import com.daniel.jsoneditor.model.json.schema.paths.PathHelper; @@ -60,6 +62,8 @@ public class ModelImpl implements ReadableModel, WritableModelInternal private Settings settings; + private final GitBlameIntegration gitBlameIntegration = new GitBlameIntegration(); + public ModelImpl(EventSender eventSender) { this.eventSender = eventSender; @@ -136,6 +140,10 @@ public void setSettings(Settings settings) public void setCurrentJSONFile(File json) { this.jsonFile = json; + if (json != null) + { + gitBlameIntegration.initialize(json.toPath()); + } } private void setCurrentSchemaFile(File schema) @@ -761,4 +769,16 @@ public String getIdentifier(String pathOfParentNode, JsonNode childNode) } return null; } + + @Override + public GitBlameInfo getBlameForPath(String jsonPath) + { + return gitBlameIntegration.getBlameForPath(jsonPath); + } + + @Override + public boolean isGitBlameAvailable() + { + return gitBlameIntegration.isAvailable(); + } } diff --git a/src/main/java/com/daniel/jsoneditor/model/json/schema/reference/ReferenceToObjectInstance.java b/src/main/java/com/daniel/jsoneditor/model/json/schema/reference/ReferenceToObjectInstance.java index ce83b03..78effab 100644 --- a/src/main/java/com/daniel/jsoneditor/model/json/schema/reference/ReferenceToObjectInstance.java +++ b/src/main/java/com/daniel/jsoneditor/model/json/schema/reference/ReferenceToObjectInstance.java @@ -61,6 +61,9 @@ public String getRemarks() public boolean refersToObject(ReferenceableObjectInstance objectInstance) { - return objectInstance.getKey().equals(key) && objectInstance.getReferencingKey().equals(referencingKey); + final String objectKey = objectInstance.getKey(); + final String objectReferencingKey = objectInstance.getReferencingKey(); + return objectKey != null && objectKey.equals(key) + && objectReferencingKey != null && objectReferencingKey.equals(referencingKey); } } diff --git a/src/main/java/com/daniel/jsoneditor/view/impl/jfx/buttons/WindowGitBlameToggleButton.java b/src/main/java/com/daniel/jsoneditor/view/impl/jfx/buttons/WindowGitBlameToggleButton.java new file mode 100644 index 0000000..4a48bd3 --- /dev/null +++ b/src/main/java/com/daniel/jsoneditor/view/impl/jfx/buttons/WindowGitBlameToggleButton.java @@ -0,0 +1,28 @@ +package com.daniel.jsoneditor.view.impl.jfx.buttons; + +import com.daniel.jsoneditor.view.impl.jfx.impl.scenes.impl.editor.components.editorwindow.JsonEditorEditorWindow; +import javafx.scene.control.Button; +import javafx.scene.control.Tooltip; + +public class WindowGitBlameToggleButton extends Button +{ + private final JsonEditorEditorWindow window; + + public WindowGitBlameToggleButton(JsonEditorEditorWindow window) + { + super(); + this.window = window; + ButtonHelper.setButtonImage(this, "/icons/material/darkmode/outline_git_blame_white_24dp.png"); + updateState(); + setOnAction(event -> { + window.toggleGitBlameForAllTables(); + updateState(); + }); + } + + private void updateState() + { + final boolean visible = window.isGitBlameVisible(); + setTooltip(new Tooltip(visible ? "Hide Git Blame" : "Show Git Blame")); + } +} diff --git a/src/main/java/com/daniel/jsoneditor/view/impl/jfx/impl/scenes/impl/editor/components/editorwindow/JsonEditorEditorWindow.java b/src/main/java/com/daniel/jsoneditor/view/impl/jfx/impl/scenes/impl/editor/components/editorwindow/JsonEditorEditorWindow.java index abbceae..77c991c 100644 --- a/src/main/java/com/daniel/jsoneditor/view/impl/jfx/impl/scenes/impl/editor/components/editorwindow/JsonEditorEditorWindow.java +++ b/src/main/java/com/daniel/jsoneditor/view/impl/jfx/impl/scenes/impl/editor/components/editorwindow/JsonEditorEditorWindow.java @@ -49,16 +49,18 @@ public class JsonEditorEditorWindow extends VBox private List childTableViews; + private boolean gitBlameVisible = false; + public JsonEditorEditorWindow(EditorWindowManager manager, ReadableModel model, Controller controller) { this.model = model; this.manager = manager; this.controller = controller; - nameBar = new JsonEditorNamebar(manager, this, model, controller); childTableViews = new ArrayList<>(); editorTables = new AutoAdjustingSplitPane(); editorTables.setOrientation(javafx.geometry.Orientation.VERTICAL); mainTableView = new EditorTableViewImpl(manager, this, model, controller); + nameBar = new JsonEditorNamebar(manager, this, model, controller); buttonBar = new TableViewButtonBar(model, controller, mainTableView::getCurrentlyDisplayedPaths, () -> selectedPath); VBox.setVgrow(buttonBar, Priority.NEVER); @@ -336,6 +338,27 @@ public void handleSettingsChanged() setSelectedPath(selectedPath); } + /** + * Toggle git blame column visibility for all tables in this window + */ + public void toggleGitBlameForAllTables() + { + gitBlameVisible = !gitBlameVisible; + mainTableView.toggleGitBlameColumn(); + for (TableViewWithCompactNamebar childTable : childTableViews) + { + childTable.toggleGitBlame(); + } + } + + /** + * @return true if git blame columns are currently visible + */ + public boolean isGitBlameVisible() + { + return gitBlameVisible; + } + @Override protected double computePrefWidth(double v) { diff --git a/src/main/java/com/daniel/jsoneditor/view/impl/jfx/impl/scenes/impl/editor/components/editorwindow/components/JsonEditorNamebar.java b/src/main/java/com/daniel/jsoneditor/view/impl/jfx/impl/scenes/impl/editor/components/editorwindow/components/JsonEditorNamebar.java index 62fe456..7513665 100644 --- a/src/main/java/com/daniel/jsoneditor/view/impl/jfx/impl/scenes/impl/editor/components/editorwindow/components/JsonEditorNamebar.java +++ b/src/main/java/com/daniel/jsoneditor/view/impl/jfx/impl/scenes/impl/editor/components/editorwindow/components/JsonEditorNamebar.java @@ -6,6 +6,7 @@ import com.daniel.jsoneditor.model.ReadableModel; import com.daniel.jsoneditor.view.impl.jfx.buttons.ButtonHelper; import com.daniel.jsoneditor.view.impl.jfx.buttons.ShowUsagesButton; +import com.daniel.jsoneditor.view.impl.jfx.buttons.WindowGitBlameToggleButton; import com.daniel.jsoneditor.view.impl.jfx.dialogs.AreYouSureDialog; import com.daniel.jsoneditor.view.impl.jfx.impl.scenes.impl.editor.components.editorwindow.EditorWindowManager; import com.daniel.jsoneditor.view.impl.jfx.impl.scenes.impl.editor.components.editorwindow.JsonEditorEditorWindow; @@ -48,8 +49,15 @@ public JsonEditorNamebar(EditorWindowManager manager, JsonEditorEditorWindow edi nameLabel.setAlignment(Pos.CENTER); HBox.setHgrow(nameLabel, Priority.ALWAYS); showUsagesButton = new ShowUsagesButton(model, manager); - this.getChildren().addAll(makeSelectInNavbarButton(), makeGoToParentButton(), nameLabel, showUsagesButton, makeDeleteItemButton(), - makeCloseWindowButton()); + + this.getChildren().addAll(makeSelectInNavbarButton(), makeGoToParentButton(), nameLabel, showUsagesButton); + + if (model.isGitBlameAvailable()) + { + this.getChildren().add(new WindowGitBlameToggleButton(editorWindow)); + } + + this.getChildren().addAll(makeDeleteItemButton(), makeCloseWindowButton()); } public void setSelection(JsonNodeWithPath selection) diff --git a/src/main/java/com/daniel/jsoneditor/view/impl/jfx/impl/scenes/impl/editor/components/editorwindow/components/TableViewWithCompactNamebar.java b/src/main/java/com/daniel/jsoneditor/view/impl/jfx/impl/scenes/impl/editor/components/editorwindow/components/TableViewWithCompactNamebar.java index 06ba97a..37e5cb9 100644 --- a/src/main/java/com/daniel/jsoneditor/view/impl/jfx/impl/scenes/impl/editor/components/editorwindow/components/TableViewWithCompactNamebar.java +++ b/src/main/java/com/daniel/jsoneditor/view/impl/jfx/impl/scenes/impl/editor/components/editorwindow/components/TableViewWithCompactNamebar.java @@ -184,4 +184,9 @@ public void handleSorted(String path) { tableView.handleSorted(path); } + + public void toggleGitBlame() + { + tableView.toggleGitBlameColumn(); + } } diff --git a/src/main/java/com/daniel/jsoneditor/view/impl/jfx/impl/scenes/impl/editor/components/editorwindow/components/tableview/EditorTableView.java b/src/main/java/com/daniel/jsoneditor/view/impl/jfx/impl/scenes/impl/editor/components/editorwindow/components/tableview/EditorTableView.java index fb24687..de2a428 100644 --- a/src/main/java/com/daniel/jsoneditor/view/impl/jfx/impl/scenes/impl/editor/components/editorwindow/components/tableview/EditorTableView.java +++ b/src/main/java/com/daniel/jsoneditor/view/impl/jfx/impl/scenes/impl/editor/components/editorwindow/components/tableview/EditorTableView.java @@ -49,4 +49,14 @@ public abstract class EditorTableView extends TableView * Returns true if all columns are currently shown due to temporary override */ public abstract boolean isTemporaryShowAllColumns(); + + /** + * Toggles the visibility of the git blame column + */ + public abstract void toggleGitBlameColumn(); + + /** + * Returns true if git blame column is currently visible + */ + public abstract boolean isGitBlameColumnVisible(); } diff --git a/src/main/java/com/daniel/jsoneditor/view/impl/jfx/impl/scenes/impl/editor/components/editorwindow/components/tableview/impl/EditorTableViewImpl.java b/src/main/java/com/daniel/jsoneditor/view/impl/jfx/impl/scenes/impl/editor/components/editorwindow/components/tableview/impl/EditorTableViewImpl.java index 0ca42f6..2473c58 100644 --- a/src/main/java/com/daniel/jsoneditor/view/impl/jfx/impl/scenes/impl/editor/components/editorwindow/components/tableview/impl/EditorTableViewImpl.java +++ b/src/main/java/com/daniel/jsoneditor/view/impl/jfx/impl/scenes/impl/editor/components/editorwindow/components/tableview/impl/EditorTableViewImpl.java @@ -12,6 +12,7 @@ import com.daniel.jsoneditor.view.impl.jfx.impl.scenes.impl.editor.components.editorwindow.JsonEditorEditorWindow; import com.daniel.jsoneditor.view.impl.jfx.impl.scenes.impl.editor.components.editorwindow.components.tableview.EditorTableView; import com.daniel.jsoneditor.view.impl.jfx.impl.scenes.impl.editor.components.editorwindow.components.tableview.impl.columns.EditorTableColumn; +import com.daniel.jsoneditor.view.impl.jfx.impl.scenes.impl.editor.components.editorwindow.components.tableview.impl.columns.GitBlameColumn; import com.fasterxml.jackson.databind.JsonNode; import javafx.collections.ObservableList; import javafx.collections.transformation.FilteredList; @@ -22,7 +23,6 @@ import javafx.scene.input.KeyEvent; import javafx.scene.layout.Priority; import javafx.scene.layout.VBox; -import javafx.util.Callback; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -60,6 +60,8 @@ public class EditorTableViewImpl extends EditorTableView // temporary override for hide empty columns setting private boolean temporaryShowAllColumns = false; + private boolean gitBlameColumnVisible = false; + private void refreshTable() { final JsonNodeWithPath parentNode = model.getNodeForPath(parentPath); @@ -445,4 +447,28 @@ private void updatePathsAfterIndex(int startListIndex, int startArrayIndex) } } } + + @Override + public void toggleGitBlameColumn() + { + gitBlameColumnVisible = !gitBlameColumnVisible; + refreshGitBlameColumnVisibility(); + } + + @Override + public boolean isGitBlameColumnVisible() + { + return gitBlameColumnVisible; + } + + private void refreshGitBlameColumnVisibility() + { + for (TableColumn column : getColumns()) + { + if (column instanceof GitBlameColumn) + { + column.setVisible(gitBlameColumnVisible); + } + } + } } diff --git a/src/main/java/com/daniel/jsoneditor/view/impl/jfx/impl/scenes/impl/editor/components/editorwindow/components/tableview/impl/TableColumnFactory.java b/src/main/java/com/daniel/jsoneditor/view/impl/jfx/impl/scenes/impl/editor/components/editorwindow/components/tableview/impl/TableColumnFactory.java index b7c09f9..d914976 100644 --- a/src/main/java/com/daniel/jsoneditor/view/impl/jfx/impl/scenes/impl/editor/components/editorwindow/components/tableview/impl/TableColumnFactory.java +++ b/src/main/java/com/daniel/jsoneditor/view/impl/jfx/impl/scenes/impl/editor/components/editorwindow/components/tableview/impl/TableColumnFactory.java @@ -8,6 +8,7 @@ import com.daniel.jsoneditor.view.impl.jfx.impl.scenes.impl.editor.components.editorwindow.JsonEditorEditorWindow; import com.daniel.jsoneditor.view.impl.jfx.impl.scenes.impl.editor.components.editorwindow.components.tableview.impl.columns.EditorTableColumn; import com.daniel.jsoneditor.view.impl.jfx.impl.scenes.impl.editor.components.editorwindow.components.tableview.impl.columns.FollowRefOrOpenColumn; +import com.daniel.jsoneditor.view.impl.jfx.impl.scenes.impl.editor.components.editorwindow.components.tableview.impl.columns.GitBlameColumn; import com.fasterxml.jackson.databind.JsonNode; import javafx.beans.property.SimpleStringProperty; import javafx.scene.control.Button; @@ -53,6 +54,13 @@ public List> createColumns( { final List> columns = new ArrayList<>(createPropertyColumns(properties, parentTableView)); + if (model.isGitBlameAvailable()) + { + final GitBlameColumn blameColumn = new GitBlameColumn(model); + blameColumn.setVisible(false); + columns.add(blameColumn); + } + if (isArray) { columns.add(new FollowRefOrOpenColumn(model, manager)); diff --git a/src/main/java/com/daniel/jsoneditor/view/impl/jfx/impl/scenes/impl/editor/components/editorwindow/components/tableview/impl/columns/GitBlameColumn.java b/src/main/java/com/daniel/jsoneditor/view/impl/jfx/impl/scenes/impl/editor/components/editorwindow/components/tableview/impl/columns/GitBlameColumn.java new file mode 100644 index 0000000..3ed801c --- /dev/null +++ b/src/main/java/com/daniel/jsoneditor/view/impl/jfx/impl/scenes/impl/editor/components/editorwindow/components/tableview/impl/columns/GitBlameColumn.java @@ -0,0 +1,110 @@ +package com.daniel.jsoneditor.view.impl.jfx.impl.scenes.impl.editor.components.editorwindow.components.tableview.impl.columns; + +import com.daniel.jsoneditor.model.ReadableModel; +import com.daniel.jsoneditor.model.git.GitBlameInfo; +import com.daniel.jsoneditor.model.json.JsonNodeWithPath; +import javafx.beans.property.SimpleStringProperty; +import javafx.geometry.Insets; +import javafx.geometry.Pos; +import javafx.scene.control.Label; +import javafx.scene.control.TableCell; +import javafx.scene.control.TableColumn; +import javafx.scene.control.Tooltip; +import javafx.scene.layout.HBox; +import javafx.scene.shape.Rectangle; +import javafx.util.Duration; + +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; + +/** + * Table column showing git blame information (last author and commit). + */ +public class GitBlameColumn extends TableColumn +{ + private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm") + .withZone(ZoneId.systemDefault()); + + private final ReadableModel model; + + public GitBlameColumn(ReadableModel model) + { + super("Last Modified"); + this.model = model; + + setMinWidth(100); + setPrefWidth(150); + setSortable(false); + + setCellValueFactory(data -> { + final JsonNodeWithPath nodeWithPath = data.getValue(); + final GitBlameInfo blameInfo = model.getBlameForPath(nodeWithPath.getPath()); + + if (blameInfo != null) + { + return new SimpleStringProperty(blameInfo.toString()); + } + return new SimpleStringProperty(""); + }); + + setCellFactory(column -> new TableCell<>() + { + private final Rectangle colorIndicator = new Rectangle(8, 16); + private final Label textLabel = new Label(); + private final HBox content = new HBox(5); + + { + colorIndicator.setArcWidth(3); + colorIndicator.setArcHeight(3); + content.setAlignment(Pos.CENTER_LEFT); + content.setPadding(new Insets(2, 0, 2, 0)); + content.getChildren().addAll(colorIndicator, textLabel); + } + + @Override + protected void updateItem(String item, boolean empty) + { + super.updateItem(item, empty); + + if (empty || item == null || item.isEmpty()) + { + setText(null); + setGraphic(null); + setTooltip(null); + return; + } + + setText(null); + textLabel.setText(item); + + final JsonNodeWithPath nodeWithPath = getTableRow().getItem(); + if (nodeWithPath != null) + { + final GitBlameInfo blameInfo = model.getBlameForPath(nodeWithPath.getPath()); + if (blameInfo != null) + { + colorIndicator.setStyle("-fx-fill: " + blameInfo.getCommitColor() + ";"); + + final String tooltipText = String.format( + "Author: %s <%s>\nCommit: %s\nDate: %s\n\n%s", + blameInfo.getAuthorName(), + blameInfo.getAuthorEmail(), + blameInfo.getShortCommitHash(), + DATE_FORMATTER.format(blameInfo.getCommitTime()), + blameInfo.getShortCommitMessage() + ); + + final Tooltip tooltip = new Tooltip(tooltipText); + tooltip.setShowDelay(Duration.millis(300)); + setTooltip(tooltip); + + setGraphic(content); + return; + } + } + + setGraphic(null); + } + }); + } +} diff --git a/src/main/resources/icons/material/darkmode/outline_git_blame_white_24dp.png b/src/main/resources/icons/material/darkmode/outline_git_blame_white_24dp.png new file mode 100644 index 0000000000000000000000000000000000000000..9b2a9b22e286a63e49f841405e0a5f6e58678550 GIT binary patch literal 630 zcmV-+0*U>JP);#y+6L6_qp%A?>(>J=<-Wo%~vcI#}H%5=kslv ziDfGwV4{K`#Ow9?0${w&Kmarq%Ty;4xKJpB024!T1^0lFI!pl*iG&!7#de|08bCru zFtmDRfOu_>2pDYx>J1Y(M7txu-=EV|&Oqe}Qf2}u%W@h)XJ~K;oUYYs+YK068>E3Q zU@n*QilP`K{mSL?b0iXZr^fkB8K_rp)B|X_QmJH|PG??wwgpv^2K}#5KSfipR4UD4 zf|sPOTCGk8gTb4Q0JGU_kH_OtNhhdy45e1{`MlIMfHW8mNO{M)>mXOq>PNyAS6`b{S7?_4(1n4annV3LX#?{#83+V!J2Fiz z3hKqWM@3Rq)w6Io{L$tFzM4V7d4QdgGF;s_T-}{jz$)@=4}+y7Nm4q2p|wT2*s9LP+EnyCCfXd;rj>T~K=CnEFT> zgJGsJ%7ZK+O)WFcK-ew9Znt|M&xTll;_PP~XFp`g`f-^)VPyfz0a>W}3Ak>@M@wLp QY5)KL07*qoM6N<$f(hvrX#fBK literal 0 HcmV?d00001 diff --git a/src/main/resources/version.properties b/src/main/resources/version.properties index d3f36aa..e813e4f 100644 --- a/src/main/resources/version.properties +++ b/src/main/resources/version.properties @@ -1 +1 @@ -version=0.15.1 \ No newline at end of file +version=0.16.0 \ No newline at end of file From 71480683d3024d126722f412207e522221a96d83 Mon Sep 17 00:00:00 2001 From: Daniel Date: Sat, 31 Jan 2026 16:29:00 +0100 Subject: [PATCH 2/5] smol cleanup --- .../model/git/GitBlameIntegration.java | 24 ++++--------------- .../jsoneditor/model/git/GitBlameService.java | 13 ---------- 2 files changed, 5 insertions(+), 32 deletions(-) diff --git a/src/main/java/com/daniel/jsoneditor/model/git/GitBlameIntegration.java b/src/main/java/com/daniel/jsoneditor/model/git/GitBlameIntegration.java index c409231..f0f9684 100644 --- a/src/main/java/com/daniel/jsoneditor/model/git/GitBlameIntegration.java +++ b/src/main/java/com/daniel/jsoneditor/model/git/GitBlameIntegration.java @@ -24,17 +24,17 @@ public class GitBlameIntegration * Initialize with a JSON file. * Checks if file is in a git repository and builds path-to-line mapping. * - * @param jsonFilePath absolute path to JSON file - * @return true if successfully initialized + * @param jsonFilePath + * absolute path to JSON file */ - public boolean initialize(Path jsonFilePath) + public void initialize(Path jsonFilePath) { close(); if (!blameService.initialize(jsonFilePath)) { logger.debug("Git blame not available for: {}", jsonFilePath); - return false; + return; } relativeFilePath = blameService.getRelativePath(jsonFilePath); @@ -42,13 +42,12 @@ public boolean initialize(Path jsonFilePath) { logger.warn("Could not determine relative path for: {}", jsonFilePath); close(); - return false; + return; } lineMapper.buildMapping(jsonFilePath); initialized = true; logger.info("Git blame initialized for: {} ({})", jsonFilePath, relativeFilePath); - return true; } /** @@ -79,19 +78,6 @@ public boolean isAvailable() return initialized; } - /** - * Refresh the line mapping after file changes. - * Should be called after saving the file. - */ - public void refresh(Path jsonFilePath) - { - if (initialized) - { - lineMapper.buildMapping(jsonFilePath); - blameService.clearCache(); - } - } - public void close() { blameService.close(); diff --git a/src/main/java/com/daniel/jsoneditor/model/git/GitBlameService.java b/src/main/java/com/daniel/jsoneditor/model/git/GitBlameService.java index 6387739..ed7d8fa 100644 --- a/src/main/java/com/daniel/jsoneditor/model/git/GitBlameService.java +++ b/src/main/java/com/daniel/jsoneditor/model/git/GitBlameService.java @@ -140,19 +140,6 @@ public String getRelativePath(Path absolutePath) return null; } - public boolean isInitialized() - { - return git != null && repository != null; - } - - /** - * Clear the blame cache. Should be called when file content changes. - */ - public void clearCache() - { - blameCache.clear(); - } - /** * Close the git repository and release resources. */ From 2d1f66e04069b8b16b50dad23a95050568f1678c Mon Sep 17 00:00:00 2001 From: Daniel Date: Sat, 31 Jan 2026 16:31:40 +0100 Subject: [PATCH 3/5] more smol cleanup --- .../components/tableview/impl/columns/GitBlameColumn.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/main/java/com/daniel/jsoneditor/view/impl/jfx/impl/scenes/impl/editor/components/editorwindow/components/tableview/impl/columns/GitBlameColumn.java b/src/main/java/com/daniel/jsoneditor/view/impl/jfx/impl/scenes/impl/editor/components/editorwindow/components/tableview/impl/columns/GitBlameColumn.java index 3ed801c..6aba8e1 100644 --- a/src/main/java/com/daniel/jsoneditor/view/impl/jfx/impl/scenes/impl/editor/components/editorwindow/components/tableview/impl/columns/GitBlameColumn.java +++ b/src/main/java/com/daniel/jsoneditor/view/impl/jfx/impl/scenes/impl/editor/components/editorwindow/components/tableview/impl/columns/GitBlameColumn.java @@ -25,12 +25,9 @@ public class GitBlameColumn extends TableColumn private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm") .withZone(ZoneId.systemDefault()); - private final ReadableModel model; - public GitBlameColumn(ReadableModel model) { super("Last Modified"); - this.model = model; setMinWidth(100); setPrefWidth(150); From c027fb1da05b88886b71a2314253415be1e28151 Mon Sep 17 00:00:00 2001 From: Daniel Kispert Date: Sun, 1 Feb 2026 16:11:00 +0100 Subject: [PATCH 4/5] improved performance --- directLibs/smartgraph-2.0.0.jar | Bin 99254 -> 0 bytes .../jsoneditor/model/ReadableModel.java | 7 + .../jsoneditor/model/git/GitBlameInfo.java | 21 +-- .../model/git/GitBlameIntegration.java | 103 ++++++++--- .../model/git/JsonPathToLineMapper.java | 168 +++++++++++++++--- .../jsoneditor/model/impl/ModelImpl.java | 11 +- .../model/statemachine/impl/EventEnum.java | 4 +- .../tableview/impl/EditorTableViewImpl.java | 2 +- .../tableview/impl/TableColumnFactory.java | 4 +- .../impl/columns/GitBlameColumn.java | 88 +++++---- 10 files changed, 295 insertions(+), 113 deletions(-) delete mode 100644 directLibs/smartgraph-2.0.0.jar diff --git a/directLibs/smartgraph-2.0.0.jar b/directLibs/smartgraph-2.0.0.jar deleted file mode 100644 index 197e84820f33f0784951f1d3d708ac46b1a07e53..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 99254 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 tV2J 7 ? commitHash.substring(0, 7) : commitHash; @@ -44,11 +41,6 @@ public Instant getCommitTime() return commitTime; } - public String getCommitMessage() - { - return commitMessage; - } - public String getShortCommitMessage() { if (commitMessage == null) return ""; @@ -56,11 +48,12 @@ public String getShortCommitMessage() return newlineIndex > 0 ? commitMessage.substring(0, newlineIndex) : commitMessage; } - /** - * Get a color derived from the commit hash for visual distinction. - * Returns a web color string like "#A3B5C7" - */ public String getCommitColor() + { + return commitColor; + } + + private String calculateCommitColor() { if (commitHash == null || commitHash.isEmpty()) { diff --git a/src/main/java/com/daniel/jsoneditor/model/git/GitBlameIntegration.java b/src/main/java/com/daniel/jsoneditor/model/git/GitBlameIntegration.java index f0f9684..8da2476 100644 --- a/src/main/java/com/daniel/jsoneditor/model/git/GitBlameIntegration.java +++ b/src/main/java/com/daniel/jsoneditor/model/git/GitBlameIntegration.java @@ -4,6 +4,10 @@ import org.slf4j.LoggerFactory; import java.nio.file.Path; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.atomic.AtomicBoolean; /** * External tool integration for git blame functionality. @@ -16,72 +20,115 @@ public class GitBlameIntegration private final GitBlameService blameService = new GitBlameService(); private final JsonPathToLineMapper lineMapper = new JsonPathToLineMapper(); + private final Map pathCache = new HashMap<>(); private String relativeFilePath; - private boolean initialized = false; + private final AtomicBoolean initialized = new AtomicBoolean(false); + private final AtomicBoolean initializing = new AtomicBoolean(false); /** - * Initialize with a JSON file. - * Checks if file is in a git repository and builds path-to-line mapping. + * Initialize with a JSON file asynchronously. + * Returns immediately, loading happens in background thread. * - * @param jsonFilePath - * absolute path to JSON file + * @param jsonFilePath absolute path to JSON file + * @return CompletableFuture that completes when initialization is done */ - public void initialize(Path jsonFilePath) + public CompletableFuture initialize(Path jsonFilePath) { close(); + initializing.set(true); - if (!blameService.initialize(jsonFilePath)) - { - logger.debug("Git blame not available for: {}", jsonFilePath); - return; - } - - relativeFilePath = blameService.getRelativePath(jsonFilePath); - if (relativeFilePath == null) - { - logger.warn("Could not determine relative path for: {}", jsonFilePath); - close(); - return; - } - - lineMapper.buildMapping(jsonFilePath); - initialized = true; - logger.info("Git blame initialized for: {} ({})", jsonFilePath, relativeFilePath); + return CompletableFuture.runAsync(() -> { + try + { + if (!blameService.initialize(jsonFilePath)) + { + logger.debug("Git blame not available for: {}", jsonFilePath); + return; + } + + relativeFilePath = blameService.getRelativePath(jsonFilePath); + if (relativeFilePath == null) + { + logger.warn("Could not determine relative path for: {}", jsonFilePath); + close(); + return; + } + + lineMapper.buildMapping(jsonFilePath); + synchronized (pathCache) + { + pathCache.clear(); + } + initialized.set(true); + logger.info("Git blame initialized for: {} ({})", jsonFilePath, relativeFilePath); + } + finally + { + initializing.set(false); + } + }); } /** * Get blame information for a JSON path. * * @param jsonPath JSON path (e.g., "/root/child/property") - * @return blame info or null if not available + * @return blame info or null if not available or still loading */ public GitBlameInfo getBlameForPath(String jsonPath) { - if (!initialized) + if (!initialized.get()) { return null; } + synchronized (pathCache) + { + if (pathCache.containsKey(jsonPath)) + { + return pathCache.get(jsonPath); + } + } + final int lineNumber = lineMapper.getLineForPathOrParent(jsonPath); if (lineNumber < 0) { logger.debug("No line number found for path: {}", jsonPath); + synchronized (pathCache) + { + pathCache.put(jsonPath, null); + } return null; } - return blameService.getBlameForLine(relativeFilePath, lineNumber); + final GitBlameInfo blameInfo = blameService.getBlameForLine(relativeFilePath, lineNumber); + synchronized (pathCache) + { + pathCache.put(jsonPath, blameInfo); + } + return blameInfo; } public boolean isAvailable() { - return initialized; + return initialized.get() || initializing.get(); + } + + public boolean isLoading() + { + return initializing.get(); } public void close() { blameService.close(); - initialized = false; + synchronized (pathCache) + { + pathCache.clear(); + } + initialized.set(false); + initializing.set(false); relativeFilePath = null; } } diff --git a/src/main/java/com/daniel/jsoneditor/model/git/JsonPathToLineMapper.java b/src/main/java/com/daniel/jsoneditor/model/git/JsonPathToLineMapper.java index f1634e4..af38d61 100644 --- a/src/main/java/com/daniel/jsoneditor/model/git/JsonPathToLineMapper.java +++ b/src/main/java/com/daniel/jsoneditor/model/git/JsonPathToLineMapper.java @@ -1,81 +1,193 @@ package com.daniel.jsoneditor.model.git; +import com.fasterxml.jackson.core.JsonFactory; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonToken; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; -import java.nio.file.Files; import java.nio.file.Path; -import java.util.List; +import java.util.ArrayDeque; +import java.util.Deque; +import java.util.HashMap; +import java.util.Map; /** - * Simple mapper that finds line numbers for JSON property keys. - * For MVP: searches for the last path segment as a JSON key. + * Maps JSON paths to line numbers by parsing the JSON file with Jackson's streaming API. + * Builds a complete path-to-line mapping during initialization for fast lookups. */ public class JsonPathToLineMapper { private static final Logger logger = LoggerFactory.getLogger(JsonPathToLineMapper.class); - private List lines; + private final Map pathToLineMap = new HashMap<>(); + private final JsonFactory jsonFactory = new JsonFactory(); + /** + * Parse JSON file and build complete path-to-line mapping. + */ public void buildMapping(Path jsonFilePath) { - try + pathToLineMap.clear(); + + try (JsonParser parser = jsonFactory.createParser(jsonFilePath.toFile())) { - lines = Files.readAllLines(jsonFilePath); + final Deque pathStack = new ArrayDeque<>(); + final Deque arrayIndexStack = new ArrayDeque<>(); + String currentFieldName = null; + + while (parser.nextToken() != null) + { + final JsonToken token = parser.currentToken(); + final int lineNumber = parser.getTokenLocation().getLineNr() - 1; + + switch (token) + { + case FIELD_NAME: + currentFieldName = parser.currentName(); + break; + + case START_OBJECT: + if (currentFieldName != null) + { + pathStack.push(currentFieldName); + final String path = buildPath(pathStack); + pathToLineMap.put(path, lineNumber); + currentFieldName = null; + } + else if (!pathStack.isEmpty() && isArrayContext(arrayIndexStack)) + { + final int arrayIndex = arrayIndexStack.pop(); + pathStack.push(String.valueOf(arrayIndex)); + final String path = buildPath(pathStack); + pathToLineMap.put(path, lineNumber); + arrayIndexStack.push(arrayIndex + 1); + } + arrayIndexStack.push(0); + break; + + case END_OBJECT: + if (!pathStack.isEmpty()) + { + pathStack.pop(); + } + if (!arrayIndexStack.isEmpty()) + { + arrayIndexStack.pop(); + } + break; + + case START_ARRAY: + if (currentFieldName != null) + { + pathStack.push(currentFieldName); + final String path = buildPath(pathStack); + pathToLineMap.put(path, lineNumber); + currentFieldName = null; + } + arrayIndexStack.push(0); + break; + + case END_ARRAY: + if (!pathStack.isEmpty()) + { + pathStack.pop(); + } + if (!arrayIndexStack.isEmpty()) + { + arrayIndexStack.pop(); + } + break; + + default: + if (currentFieldName != null) + { + pathStack.push(currentFieldName); + final String path = buildPath(pathStack); + pathToLineMap.put(path, lineNumber); + pathStack.pop(); + currentFieldName = null; + } + else if (!pathStack.isEmpty() && isArrayContext(arrayIndexStack)) + { + final int arrayIndex = arrayIndexStack.pop(); + pathStack.push(String.valueOf(arrayIndex)); + final String path = buildPath(pathStack); + pathToLineMap.put(path, lineNumber); + pathStack.pop(); + arrayIndexStack.push(arrayIndex + 1); + } + break; + } + } + + logger.debug("Built path-to-line mapping with {} entries", pathToLineMap.size()); } catch (IOException e) { - logger.error("Failed to read JSON file: {}", jsonFilePath, e); - lines = null; + logger.error("Failed to parse JSON file: {}", jsonFilePath, e); } } /** - * Find line number for a JSON path by searching for the last path segment. + * Get line number for a JSON path, or search for parent paths if not found. * * @param fullPath JSON path like "/root/child/property" * @return 0-based line number or -1 if not found */ public int getLineForPathOrParent(String fullPath) { - if (lines == null || fullPath == null || fullPath.isEmpty()) + if (fullPath == null || fullPath.isEmpty()) { - return -1; + return 0; } - final String lastSegment = getLastSegment(fullPath); - if (lastSegment == null) + String searchPath = fullPath.startsWith("/") ? fullPath.substring(1) : fullPath; + + if (pathToLineMap.containsKey(searchPath)) { - return 0; + return pathToLineMap.get(searchPath); } - final String searchPattern = "\"" + lastSegment + "\""; - - for (int i = 0; i < lines.size(); i++) + while (searchPath.contains("/")) { - if (lines.get(i).contains(searchPattern)) + final int lastSlash = searchPath.lastIndexOf('/'); + searchPath = searchPath.substring(0, lastSlash); + + if (pathToLineMap.containsKey(searchPath)) { - return i; + return pathToLineMap.get(searchPath); } } - return -1; + return pathToLineMap.getOrDefault(searchPath, -1); } - private String getLastSegment(String path) + private String buildPath(Deque pathStack) { - if (path == null || path.isEmpty()) + if (pathStack.isEmpty()) { - return null; + return ""; } - final int lastSlash = path.lastIndexOf('/'); - if (lastSlash < 0 || lastSlash == path.length() - 1) + final StringBuilder sb = new StringBuilder(); + final Object[] pathParts = pathStack.toArray(); + + for (int i = pathParts.length - 1; i >= 0; i--) { - return null; + sb.append(pathParts[i]); + if (i > 0) + { + sb.append('/'); + } } - return path.substring(lastSlash + 1); + return sb.toString(); + } + + private boolean isArrayContext(Deque arrayIndexStack) + { + return !arrayIndexStack.isEmpty() && arrayIndexStack.peek() >= 0; } } diff --git a/src/main/java/com/daniel/jsoneditor/model/impl/ModelImpl.java b/src/main/java/com/daniel/jsoneditor/model/impl/ModelImpl.java index 2d2a1da..09a41f9 100644 --- a/src/main/java/com/daniel/jsoneditor/model/impl/ModelImpl.java +++ b/src/main/java/com/daniel/jsoneditor/model/impl/ModelImpl.java @@ -142,7 +142,10 @@ public void setCurrentJSONFile(File json) this.jsonFile = json; if (json != null) { - gitBlameIntegration.initialize(json.toPath()); + gitBlameIntegration.initialize(json.toPath()).thenRun(() -> { + logger.info("Git blame loading completed"); + sendEvent(new Event(EventEnum.GIT_BLAME_LOADED)); + }); } } @@ -781,4 +784,10 @@ public boolean isGitBlameAvailable() { return gitBlameIntegration.isAvailable(); } + + @Override + public boolean isGitBlameLoading() + { + return gitBlameIntegration.isLoading(); + } } diff --git a/src/main/java/com/daniel/jsoneditor/model/statemachine/impl/EventEnum.java b/src/main/java/com/daniel/jsoneditor/model/statemachine/impl/EventEnum.java index 35228b3..48b3d9b 100644 --- a/src/main/java/com/daniel/jsoneditor/model/statemachine/impl/EventEnum.java +++ b/src/main/java/com/daniel/jsoneditor/model/statemachine/impl/EventEnum.java @@ -32,5 +32,7 @@ public enum EventEnum SAVING_FAILED, - COMMAND_APPLIED // new command (execute/undo/redo) with metadata + COMMAND_APPLIED, // new command (execute/undo/redo) with metadata + + GIT_BLAME_LOADED } diff --git a/src/main/java/com/daniel/jsoneditor/view/impl/jfx/impl/scenes/impl/editor/components/editorwindow/components/tableview/impl/EditorTableViewImpl.java b/src/main/java/com/daniel/jsoneditor/view/impl/jfx/impl/scenes/impl/editor/components/editorwindow/components/tableview/impl/EditorTableViewImpl.java index 2473c58..0d56f09 100644 --- a/src/main/java/com/daniel/jsoneditor/view/impl/jfx/impl/scenes/impl/editor/components/editorwindow/components/tableview/impl/EditorTableViewImpl.java +++ b/src/main/java/com/daniel/jsoneditor/view/impl/jfx/impl/scenes/impl/editor/components/editorwindow/components/tableview/impl/EditorTableViewImpl.java @@ -235,7 +235,7 @@ private void setView(TableSchemaProcessor.TableData tableData) this.allItems = tableData.getNodes(); // Use the column factory to create columns - final List> columns = columnFactory.createColumns(tableData.getProperties(), + final List> columns = columnFactory.createColumns(tableData.getProperties(), tableData.isArray(), this); filteredItems = new FilteredList<>(allItems); diff --git a/src/main/java/com/daniel/jsoneditor/view/impl/jfx/impl/scenes/impl/editor/components/editorwindow/components/tableview/impl/TableColumnFactory.java b/src/main/java/com/daniel/jsoneditor/view/impl/jfx/impl/scenes/impl/editor/components/editorwindow/components/tableview/impl/TableColumnFactory.java index d914976..d96fa40 100644 --- a/src/main/java/com/daniel/jsoneditor/view/impl/jfx/impl/scenes/impl/editor/components/editorwindow/components/tableview/impl/TableColumnFactory.java +++ b/src/main/java/com/daniel/jsoneditor/view/impl/jfx/impl/scenes/impl/editor/components/editorwindow/components/tableview/impl/TableColumnFactory.java @@ -47,12 +47,12 @@ public TableColumnFactory(EditorWindowManager manager, Controller controller, * @param parentTableView reference to the parent table view * @return list of created columns */ - public List> createColumns( + public List> createColumns( List, JsonNode>> properties, boolean isArray, EditorTableViewImpl parentTableView) { - final List> columns = new ArrayList<>(createPropertyColumns(properties, parentTableView)); + final List> columns = new ArrayList<>(createPropertyColumns(properties, parentTableView)); if (model.isGitBlameAvailable()) { diff --git a/src/main/java/com/daniel/jsoneditor/view/impl/jfx/impl/scenes/impl/editor/components/editorwindow/components/tableview/impl/columns/GitBlameColumn.java b/src/main/java/com/daniel/jsoneditor/view/impl/jfx/impl/scenes/impl/editor/components/editorwindow/components/tableview/impl/columns/GitBlameColumn.java index 6aba8e1..1547346 100644 --- a/src/main/java/com/daniel/jsoneditor/view/impl/jfx/impl/scenes/impl/editor/components/editorwindow/components/tableview/impl/columns/GitBlameColumn.java +++ b/src/main/java/com/daniel/jsoneditor/view/impl/jfx/impl/scenes/impl/editor/components/editorwindow/components/tableview/impl/columns/GitBlameColumn.java @@ -3,7 +3,10 @@ import com.daniel.jsoneditor.model.ReadableModel; import com.daniel.jsoneditor.model.git.GitBlameInfo; import com.daniel.jsoneditor.model.json.JsonNodeWithPath; -import javafx.beans.property.SimpleStringProperty; +import com.daniel.jsoneditor.model.observe.Observer; +import com.daniel.jsoneditor.model.statemachine.impl.EventEnum; +import javafx.application.Platform; +import javafx.beans.property.SimpleObjectProperty; import javafx.geometry.Insets; import javafx.geometry.Pos; import javafx.scene.control.Label; @@ -20,15 +23,19 @@ /** * Table column showing git blame information (last author and commit). */ -public class GitBlameColumn extends TableColumn +public class GitBlameColumn extends TableColumn implements Observer { private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm") .withZone(ZoneId.systemDefault()); + private final ReadableModel model; + public GitBlameColumn(ReadableModel model) { super("Last Modified"); + this.model = model; + setMinWidth(100); setPrefWidth(150); setSortable(false); @@ -36,12 +43,7 @@ public GitBlameColumn(ReadableModel model) setCellValueFactory(data -> { final JsonNodeWithPath nodeWithPath = data.getValue(); final GitBlameInfo blameInfo = model.getBlameForPath(nodeWithPath.getPath()); - - if (blameInfo != null) - { - return new SimpleStringProperty(blameInfo.toString()); - } - return new SimpleStringProperty(""); + return new SimpleObjectProperty<>(blameInfo); }); setCellFactory(column -> new TableCell<>() @@ -59,11 +61,11 @@ public GitBlameColumn(ReadableModel model) } @Override - protected void updateItem(String item, boolean empty) + protected void updateItem(GitBlameInfo blameInfo, boolean empty) { - super.updateItem(item, empty); + super.updateItem(blameInfo, empty); - if (empty || item == null || item.isEmpty()) + if (empty || blameInfo == null) { setText(null); setGraphic(null); @@ -72,36 +74,46 @@ protected void updateItem(String item, boolean empty) } setText(null); - textLabel.setText(item); + textLabel.setText(blameInfo.toString()); + colorIndicator.setStyle("-fx-fill: " + blameInfo.getCommitColor() + ";"); - final JsonNodeWithPath nodeWithPath = getTableRow().getItem(); - if (nodeWithPath != null) - { - final GitBlameInfo blameInfo = model.getBlameForPath(nodeWithPath.getPath()); - if (blameInfo != null) - { - colorIndicator.setStyle("-fx-fill: " + blameInfo.getCommitColor() + ";"); - - final String tooltipText = String.format( - "Author: %s <%s>\nCommit: %s\nDate: %s\n\n%s", - blameInfo.getAuthorName(), - blameInfo.getAuthorEmail(), - blameInfo.getShortCommitHash(), - DATE_FORMATTER.format(blameInfo.getCommitTime()), - blameInfo.getShortCommitMessage() - ); - - final Tooltip tooltip = new Tooltip(tooltipText); - tooltip.setShowDelay(Duration.millis(300)); - setTooltip(tooltip); - - setGraphic(content); - return; - } - } + final String tooltipText = String.format( + "Author: %s <%s>\nCommit: %s\nDate: %s\n\n%s", + blameInfo.getAuthorName(), + blameInfo.getAuthorEmail(), + blameInfo.getShortCommitHash(), + DATE_FORMATTER.format(blameInfo.getCommitTime()), + blameInfo.getShortCommitMessage() + ); - setGraphic(null); + final Tooltip tooltip = new Tooltip(tooltipText); + tooltip.setShowDelay(Duration.millis(300)); + setTooltip(tooltip); + + setGraphic(content); } }); + + model.getForObservation().registerObserver(this); + } + + @Override + public void observe(com.daniel.jsoneditor.model.observe.Subject subjectToObserve) + { + subjectToObserve.registerObserver(this); + } + + @Override + public void update() + { + if (model.getLatestEvent().getEvent() == EventEnum.GIT_BLAME_LOADED) + { + Platform.runLater(() -> { + if (getTableView() != null) + { + getTableView().refresh(); + } + }); + } } } From 3982a2b237e9d6120222ec80fdda9840825d8445 Mon Sep 17 00:00:00 2001 From: Daniel Kispert Date: Sun, 1 Feb 2026 16:18:31 +0100 Subject: [PATCH 5/5] removed unused methods --- .../java/com/daniel/jsoneditor/model/ReadableModel.java | 7 ------- .../daniel/jsoneditor/model/git/GitBlameIntegration.java | 5 ----- .../java/com/daniel/jsoneditor/model/impl/ModelImpl.java | 6 ------ 3 files changed, 18 deletions(-) diff --git a/src/main/java/com/daniel/jsoneditor/model/ReadableModel.java b/src/main/java/com/daniel/jsoneditor/model/ReadableModel.java index 5d6d81e..ec41ebf 100644 --- a/src/main/java/com/daniel/jsoneditor/model/ReadableModel.java +++ b/src/main/java/com/daniel/jsoneditor/model/ReadableModel.java @@ -122,11 +122,4 @@ public interface ReadableModel extends ReadableState */ boolean isGitBlameAvailable(); - /** - * Check if git blame is currently loading. - * - * @return true if git blame is being loaded in background - */ - boolean isGitBlameLoading(); - } diff --git a/src/main/java/com/daniel/jsoneditor/model/git/GitBlameIntegration.java b/src/main/java/com/daniel/jsoneditor/model/git/GitBlameIntegration.java index 8da2476..70a12b3 100644 --- a/src/main/java/com/daniel/jsoneditor/model/git/GitBlameIntegration.java +++ b/src/main/java/com/daniel/jsoneditor/model/git/GitBlameIntegration.java @@ -115,11 +115,6 @@ public boolean isAvailable() return initialized.get() || initializing.get(); } - public boolean isLoading() - { - return initializing.get(); - } - public void close() { blameService.close(); diff --git a/src/main/java/com/daniel/jsoneditor/model/impl/ModelImpl.java b/src/main/java/com/daniel/jsoneditor/model/impl/ModelImpl.java index 09a41f9..015ea59 100644 --- a/src/main/java/com/daniel/jsoneditor/model/impl/ModelImpl.java +++ b/src/main/java/com/daniel/jsoneditor/model/impl/ModelImpl.java @@ -784,10 +784,4 @@ public boolean isGitBlameAvailable() { return gitBlameIntegration.isAvailable(); } - - @Override - public boolean isGitBlameLoading() - { - return gitBlameIntegration.isLoading(); - } }