Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@ java {
dependencies {
implementation 'io.github.copilot-community-sdk:copilot-sdk:1.0.5'
implementation 'com.networknt:json-schema-validator:1.0.87'
implementation 'ch.qos.logback:logback-classic:1.5.13'
implementation 'ch.qos.logback:logback-classic:1.5.17'
implementation 'org.eclipse.jgit:org.eclipse.jgit:6.10.0.202406032230-r'
implementation 'com.brunomnsilva:smartgraph:2.3.0'
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.10.0'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.10.0'
Expand Down
Binary file removed directLibs/smartgraph-2.0.0.jar
Binary file not shown.
16 changes: 16 additions & 0 deletions src/main/java/com/daniel/jsoneditor/model/ReadableModel.java
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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();

}
82 changes: 82 additions & 0 deletions src/main/java/com/daniel/jsoneditor/model/git/GitBlameInfo.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
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;
private final String commitColor;

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;
this.commitColor = calculateCommitColor();
}

public String getAuthorName()
{
return authorName;
}

public String getAuthorEmail()
{
return authorEmail;
}

public String getShortCommitHash()
{
return commitHash != null && commitHash.length() > 7 ? commitHash.substring(0, 7) : commitHash;
}

public Instant getCommitTime()
{
return commitTime;
}

public String getShortCommitMessage()
{
if (commitMessage == null) return "";
final int newlineIndex = commitMessage.indexOf('\n');
return newlineIndex > 0 ? commitMessage.substring(0, newlineIndex) : commitMessage;
}

public String getCommitColor()
{
return commitColor;
}

private String calculateCommitColor()
{
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());
}
}
129 changes: 129 additions & 0 deletions src/main/java/com/daniel/jsoneditor/model/git/GitBlameIntegration.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
package com.daniel.jsoneditor.model.git;

import org.slf4j.Logger;
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.
* 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 final Map<String, GitBlameInfo> pathCache = new HashMap<>();

private String relativeFilePath;
private final AtomicBoolean initialized = new AtomicBoolean(false);
private final AtomicBoolean initializing = new AtomicBoolean(false);

/**
* Initialize with a JSON file asynchronously.
* Returns immediately, loading happens in background thread.
*
* @param jsonFilePath absolute path to JSON file
* @return CompletableFuture that completes when initialization is done
*/
public CompletableFuture<Void> initialize(Path jsonFilePath)
{
close();
initializing.set(true);

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 or still loading
*/
public GitBlameInfo getBlameForPath(String jsonPath)
{
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;
}

final GitBlameInfo blameInfo = blameService.getBlameForLine(relativeFilePath, lineNumber);
synchronized (pathCache)
{
pathCache.put(jsonPath, blameInfo);
}
return blameInfo;
}

public boolean isAvailable()
{
return initialized.get() || initializing.get();
}

public void close()
{
blameService.close();
synchronized (pathCache)
{
pathCache.clear();
}
initialized.set(false);
initializing.set(false);
relativeFilePath = null;
}
}
Loading