diff --git a/src/main/kotlin/app/git/RawFileManager.kt b/src/main/kotlin/app/git/RawFileManager.kt index 45dfe05..cb110aa 100644 --- a/src/main/kotlin/app/git/RawFileManager.kt +++ b/src/main/kotlin/app/git/RawFileManager.kt @@ -30,7 +30,6 @@ class RawFileManager @AssistedInject constructor( "png", "jpg", "jpeg", - "svg" ) init { @@ -72,7 +71,7 @@ class RawFileManager @AssistedInject constructor( println("Data's size is ${ldr.size}") val tempDir = createTempDirectory("gitnuro${repository.directory.absolutePath.replace("/", "_")}") - val tempFile = createTempFile(tempDir, prefix = "${entry.newPath}_${side.name}") + val tempFile = createTempFile(tempDir, prefix = "${entry.newPath.replace("/", "_")}_${side.name}") println("Temp file generated: ${tempFile.absolutePathString()}") val out = FileOutputStream(tempFile.toFile()) @@ -102,7 +101,8 @@ sealed class EntryContent { object Missing: EntryContent() object InvalidObjectBlob: EntryContent() data class Text(val rawText: RawText): EntryContent() - data class ImageBinary(val tempFilePath: Path): EntryContent() - object Binary: EntryContent() + sealed class BinaryContent() : EntryContent() + data class ImageBinary(val tempFilePath: Path): BinaryContent() + object Binary: BinaryContent() object TooLargeEntry: EntryContent() } \ No newline at end of file diff --git a/src/main/kotlin/app/git/diff/HunkDiffGenerator.kt b/src/main/kotlin/app/git/diff/HunkDiffGenerator.kt index 545b93c..d460dd5 100644 --- a/src/main/kotlin/app/git/diff/HunkDiffGenerator.kt +++ b/src/main/kotlin/app/git/diff/HunkDiffGenerator.kt @@ -12,11 +12,14 @@ import org.eclipse.jgit.patch.FileHeader.PatchType import org.eclipse.jgit.treewalk.AbstractTreeIterator import java.io.ByteArrayOutputStream import java.io.IOException -import java.nio.file.Path +import java.io.InvalidObjectException +import kotlin.contracts.ExperimentalContracts +import kotlin.contracts.contract import kotlin.math.max import kotlin.math.min private const val CONTEXT_LINES = 3 + /** * Generator of [Hunk] lists from [DiffEntry] */ @@ -39,19 +42,53 @@ class HunkDiffGenerator @AssistedInject constructor( diffFormatter.scan(oldTreeIterator, newTreeIterator) } - fun format(ent: DiffEntry): DiffResult { + fun format(ent: DiffEntry): DiffResult { val fileHeader = diffFormatter.toFileHeader(ent) val rawOld = rawFileManager.getRawContent(DiffEntry.Side.OLD, ent) val rawNew = rawFileManager.getRawContent(DiffEntry.Side.NEW, ent) - // todo won't work for new files - return if(rawOld is EntryContent.Text && rawNew is EntryContent.Text) - DiffResult.Text(format(fileHeader, rawOld.rawText, rawNew.rawText)) - else if(rawOld is EntryContent.ImageBinary && rawNew is EntryContent.ImageBinary) - DiffResult.Images(rawOld.tempFilePath, rawNew.tempFilePath) - else - DiffResult.Text(emptyList()) + if(rawOld == EntryContent.InvalidObjectBlob || rawNew == EntryContent.InvalidObjectBlob) + throw InvalidObjectException("Invalid object in diff format") + + var diffResult: DiffResult = DiffResult.Text(emptyList()) + + // If we can, generate text diff (if one of the files has never been a binary file) + val hasGeneratedTextDiff = canGenerateTextDiff(rawOld, rawNew) { oldRawText, newRawText -> + diffResult = DiffResult.Text(format(fileHeader, oldRawText, newRawText)) + } + + if (!hasGeneratedTextDiff) { + diffResult = DiffResult.NonText(rawOld, rawNew) + } + + return diffResult + } + + @OptIn(ExperimentalContracts::class) + private fun canGenerateTextDiff( + rawOld: EntryContent, + rawNew: EntryContent, + onText: (oldRawText: RawText, newRawText: RawText) -> Unit + ): Boolean { + + val rawOldText = when (rawOld) { + is EntryContent.Text -> rawOld.rawText + EntryContent.Missing -> RawText.EMPTY_TEXT + else -> null + } + + val newOldText = when (rawNew) { + is EntryContent.Text -> rawNew.rawText + EntryContent.Missing -> RawText.EMPTY_TEXT + else -> null + } + + return if(rawOldText != null && newOldText != null) { + onText(rawOldText, newOldText) + true + } else + false } /** @@ -155,6 +192,9 @@ class HunkDiffGenerator @AssistedInject constructor( } sealed class DiffResult { - data class Text(val hunks: List): DiffResult() - data class Images(val oldTempFile: Path, val newTempsFile: Path): DiffResult() + data class Text(val hunks: List) : DiffResult() + data class NonText( + val oldBinaryContent: EntryContent, + val newBinaryContent: EntryContent, + ) : DiffResult() } \ No newline at end of file diff --git a/src/main/kotlin/app/ui/Diff.kt b/src/main/kotlin/app/ui/Diff.kt index 6b6e22f..c6107ec 100644 --- a/src/main/kotlin/app/ui/Diff.kt +++ b/src/main/kotlin/app/ui/Diff.kt @@ -18,6 +18,7 @@ import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import app.git.DiffEntryType +import app.git.EntryContent import app.git.diff.DiffResult import app.git.diff.Hunk import app.git.diff.Line @@ -28,6 +29,7 @@ import app.ui.components.SecondaryButton import app.viewmodels.DiffViewModel import org.eclipse.jgit.diff.DiffEntry import java.io.FileInputStream +import java.nio.file.Path import kotlin.io.path.absolutePathString import kotlin.math.max @@ -52,41 +54,103 @@ fun Diff( DiffHeader(diffEntry, onCloseDiffView) if (diffResult is DiffResult.Text) { TextDiff(diffEntryType, diffViewModel, diffResult) - } else if (diffResult is DiffResult.Images) { - ImagesDiff(diffResult) + } else if (diffResult is DiffResult.NonText) { + NonTextDiff(diffResult) } } } @Composable -fun ImagesDiff(diffResult: DiffResult.Images) { - val oldImagePath = diffResult.oldTempFile - val newImagePath = diffResult.newTempsFile +fun NonTextDiff(diffResult: DiffResult.NonText) { + val oldBinaryContent = diffResult.oldBinaryContent + val newBinaryContent = diffResult.newBinaryContent + + val showOldAndNew = oldBinaryContent != EntryContent.Missing && newBinaryContent != EntryContent.Missing + Row( modifier = Modifier - .fillMaxSize() - .background(Color.Red), + .fillMaxSize(), verticalAlignment = Alignment.CenterVertically ) { - Image( - bitmap = loadImageBitmap(inputStream = FileInputStream(oldImagePath.absolutePathString())), - contentDescription = null, - modifier = Modifier.fillMaxWidth(0.5f) - .background(Color.Yellow), - ) - Spacer( - modifier = Modifier.fillMaxWidth(0.1f) - .background(Color.Green), - ) - Image( - bitmap = loadImageBitmap(inputStream = FileInputStream(newImagePath.absolutePathString())), - contentDescription = null, - modifier = Modifier.fillMaxWidth() - .background(Color.Blue), - ) + + if (showOldAndNew) { + Column( + modifier = Modifier.weight(0.5f) + .padding(start = 24.dp, end = 8.dp, top = 24.dp, bottom = 24.dp), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + SideTitle("Old") + SideDiff(oldBinaryContent) + } + Column( + modifier = Modifier.weight(0.5f) + .padding(start = 8.dp, end = 24.dp, top = 24.dp, bottom = 24.dp), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + SideTitle("New") + SideDiff(newBinaryContent) + } + } else if(oldBinaryContent != EntryContent.Missing) { + Box( + modifier = Modifier.fillMaxSize() + .padding(all = 24.dp), + ) { + SideDiff(oldBinaryContent) + } + } else if(newBinaryContent != EntryContent.Missing) { + Column( + modifier = Modifier.fillMaxSize() + .padding(all = 24.dp), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center, + ) { + SideTitle("Binary file") + Spacer(modifier = Modifier.height(24.dp)) + SideDiff(newBinaryContent) + } + } } } +@Composable +fun SideTitle(text: String) { + Text( + text = text, + fontSize = 20.sp, + color = MaterialTheme.colors.primaryTextColor, + ) +} + +@Composable +fun SideDiff(entryContent: EntryContent) { + when (entryContent) { + EntryContent.Binary -> BinaryDiff() + is EntryContent.ImageBinary -> ImageDiff(entryContent.tempFilePath) + else -> {} +// is EntryContent.Text -> //TODO maybe have a text view if the file was a binary before? +// TODO Show some info about this EntryContent.TooLargeEntry -> TODO() + } +} + +@Composable +fun ImageDiff(tempImagePath: Path) { + Image( + bitmap = loadImageBitmap(inputStream = FileInputStream(tempImagePath.absolutePathString())), + contentDescription = null, + modifier = Modifier.fillMaxSize() + ) +} + +@Composable +fun BinaryDiff() { + Image( + painter = painterResource("binary.svg"), + contentDescription = null, + modifier = Modifier.width(400.dp), + colorFilter = ColorFilter.tint(MaterialTheme.colors.primary) + ) +} + @Composable fun TextDiff(diffEntryType: DiffEntryType, diffViewModel: DiffViewModel, diffResult: DiffResult.Text) { val hunks = diffResult.hunks diff --git a/src/main/resources/binary.svg b/src/main/resources/binary.svg new file mode 100644 index 0000000..669d4ff --- /dev/null +++ b/src/main/resources/binary.svg @@ -0,0 +1,3 @@ + + +