Started implementation of full file diff (instead of hunks)

This commit is contained in:
Abdelilah El Aissaoui 2023-07-06 21:56:29 +02:00
parent 8903e473a0
commit 6ddcd0c69d
No known key found for this signature in database
GPG Key ID: 7587FC860F594869
17 changed files with 361 additions and 168 deletions

View File

@ -86,7 +86,7 @@ kotlin {
}
tasks.withType<KotlinCompile> {
kotlinOptions.allWarningsAsErrors = false
kotlinOptions.allWarningsAsErrors = true
kotlinOptions.freeCompilerArgs += "-opt-in=kotlin.RequiresOptIn"
}

View File

@ -17,6 +17,7 @@ object AppIcons {
const val COPY = "copy.svg"
const val CUT = "cut.svg"
const val DELETE = "delete.svg"
const val DESCRIPTION = "description.svg"
const val DONE = "done.svg"
const val DOWNLOAD = "download.svg"
const val DROPDOWN = "dropdown.svg"
@ -24,6 +25,7 @@ object AppIcons {
const val EXPAND_MORE = "expand_more.svg"
const val FETCH = "fetch.svg"
const val GRADE = "grade.svg"
const val HORIZONTAL_SPLIT = "horizontal_split.svg"
const val HISTORY = "history.svg"
const val INFO = "info.svg"
const val KEY = "key.svg"
@ -55,8 +57,10 @@ object AppIcons {
const val TERMINAL = "terminal.svg"
const val TOPIC = "topic.svg"
const val UNDO = "undo.svg"
const val UNIFIED = "unified.svg"
const val UPDATE = "update.svg"
const val UPLOAD = "upload.svg"
const val VERTICAL_SPLIT = "vertical_split.svg"
const val VISIBILITY = "visibility.svg"
const val VISIBILITY_OFF = "visibility_off.svg"
const val WARNING = "warning.svg"

View File

@ -34,12 +34,6 @@ val animatedImages = arrayOf(
class RawFileManager @Inject constructor(
private val tempFilesManager: TempFilesManager,
) {
private fun source(iterator: AbstractTreeIterator, reader: ObjectReader): ContentSource {
return if (iterator is WorkingTreeIterator)
ContentSource.create(iterator)
else
ContentSource.create(reader)
}
fun getRawContent(
repository: Repository,
@ -76,6 +70,13 @@ class RawFileManager @Inject constructor(
}
}
private fun source(iterator: AbstractTreeIterator, reader: ObjectReader): ContentSource {
return if (iterator is WorkingTreeIterator)
ContentSource.create(iterator)
else
ContentSource.create(reader)
}
private fun generateImageBinary(
ldr: ObjectLoader,
entry: DiffEntry,

View File

@ -0,0 +1,31 @@
package com.jetpackduba.gitnuro.git.diff
import com.jetpackduba.gitnuro.git.EntryContent
import org.eclipse.jgit.diff.RawText
import javax.inject.Inject
class CanGenerateTextDiffUseCase @Inject constructor() {
operator fun invoke(
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
}
}

View File

@ -0,0 +1,10 @@
package com.jetpackduba.gitnuro.git.diff
import com.jetpackduba.gitnuro.git.EntryContent
import org.eclipse.jgit.patch.FileHeader
data class DiffContent(
val fileHeader: FileHeader,
val rawOld: EntryContent,
val rawNew: EntryContent,
)

View File

@ -0,0 +1,24 @@
package com.jetpackduba.gitnuro.git.diff
import com.jetpackduba.gitnuro.git.EntryContent
import org.eclipse.jgit.diff.DiffEntry
sealed class DiffResult(
val diffEntry: DiffEntry,
) {
class Text(
diffEntry: DiffEntry,
val hunks: List<Hunk>
) : DiffResult(diffEntry)
class TextSplit(
diffEntry: DiffEntry,
val hunks: List<SplitHunk>
) : DiffResult(diffEntry)
class NonText(
diffEntry: DiffEntry,
val oldBinaryContent: EntryContent,
val newBinaryContent: EntryContent,
) : DiffResult(diffEntry)
}

View File

@ -1,6 +1,7 @@
package com.jetpackduba.gitnuro.git.diff
import com.jetpackduba.gitnuro.git.DiffEntryType
import com.jetpackduba.gitnuro.git.EntryContent
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.eclipse.jgit.api.Git
@ -9,13 +10,20 @@ import org.eclipse.jgit.diff.DiffFormatter
import org.eclipse.jgit.dircache.DirCacheIterator
import org.eclipse.jgit.treewalk.FileTreeIterator
import java.io.ByteArrayOutputStream
import java.io.InvalidObjectException
import javax.inject.Inject
class FormatDiffUseCase @Inject constructor(
private val hunkDiffGenerator: HunkDiffGenerator,
private val getDiffEntryForUncommitedDiffUseCase: GetDiffEntryForUncommitedDiffUseCase,
private val canGenerateTextDiffUseCase: CanGenerateTextDiffUseCase,
private val getDiffContentUseCase: GetDiffContentUseCase,
private val formatHunksUseCase: FormatHunksUseCase,
) {
suspend operator fun invoke(git: Git, diffEntryType: DiffEntryType): DiffResult = withContext(Dispatchers.IO) {
suspend operator fun invoke(
git: Git,
diffEntryType: DiffEntryType,
isDisplayFullFile: Boolean
): DiffResult = withContext(Dispatchers.IO) {
val byteArrayOutputStream = ByteArrayOutputStream()
val repository = git.repository
val diffEntry: DiffEntry
@ -54,11 +62,31 @@ class FormatDiffUseCase @Inject constructor(
newTree = null
}
return@withContext hunkDiffGenerator.format(
repository,
diffEntry,
oldTree,
newTree,
)
val diffContent = getDiffContentUseCase(repository, diffEntry, oldTree, newTree)
val fileHeader = diffContent.fileHeader
val rawOld = diffContent.rawOld
val rawNew = diffContent.rawNew
if (rawOld == EntryContent.InvalidObjectBlob || rawNew == EntryContent.InvalidObjectBlob)
throw InvalidObjectException("Invalid object in diff format")
var diffResult: DiffResult = DiffResult.Text(diffEntry, emptyList())
// If we can, generate text diff (if one of the files has never been a binary file)
val hasGeneratedTextDiff = canGenerateTextDiffUseCase(rawOld, rawNew) { oldRawText, newRawText ->
if (isDisplayFullFile) {
TODO()
} else {
diffResult = DiffResult.Text(diffEntry, formatHunksUseCase(fileHeader, oldRawText, newRawText))
}
}
if (!hasGeneratedTextDiff) {
diffResult = DiffResult.NonText(diffEntry, rawOld, rawNew)
}
return@withContext diffResult
}
}

View File

@ -1,112 +1,27 @@
package com.jetpackduba.gitnuro.git.diff
import com.jetpackduba.gitnuro.extensions.lineAt
import com.jetpackduba.gitnuro.git.EntryContent
import com.jetpackduba.gitnuro.git.RawFileManager
import org.eclipse.jgit.diff.*
import org.eclipse.jgit.lib.Repository
import org.eclipse.jgit.patch.FileHeader
import org.eclipse.jgit.patch.FileHeader.PatchType
import org.eclipse.jgit.treewalk.AbstractTreeIterator
import java.io.ByteArrayOutputStream
import java.io.IOException
import java.io.InvalidObjectException
import javax.inject.Inject
import kotlin.contracts.ExperimentalContracts
import kotlin.math.max
import kotlin.math.min
private const val CONTEXT_LINES = 3
private const val CONTEXT_LINES = 2
/**
* Generator of [Hunk] lists from [DiffEntry]
*/
class HunkDiffGenerator @Inject constructor(
private val rawFileManager: RawFileManager,
) {
fun format(
repository: Repository,
diffEntry: DiffEntry,
oldTreeIterator: AbstractTreeIterator?,
newTreeIterator: AbstractTreeIterator?,
): DiffResult {
val outputStream = ByteArrayOutputStream() // Dummy output stream used for the diff formatter
return outputStream.use {
val diffFormatter = DiffFormatter(outputStream).apply {
setRepository(repository)
}
if (oldTreeIterator != null && newTreeIterator != null) {
diffFormatter.scan(oldTreeIterator, newTreeIterator)
}
val fileHeader = diffFormatter.toFileHeader(diffEntry)
val rawOld = rawFileManager.getRawContent(
repository,
DiffEntry.Side.OLD,
diffEntry,
oldTreeIterator,
newTreeIterator
)
val rawNew = rawFileManager.getRawContent(
repository,
DiffEntry.Side.NEW,
diffEntry,
oldTreeIterator,
newTreeIterator
)
if (rawOld == EntryContent.InvalidObjectBlob || rawNew == EntryContent.InvalidObjectBlob)
throw InvalidObjectException("Invalid object in diff format")
var diffResult: DiffResult = DiffResult.Text(diffEntry, 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(diffEntry, format(fileHeader, oldRawText, newRawText))
}
if (!hasGeneratedTextDiff) {
diffResult = DiffResult.NonText(diffEntry, rawOld, rawNew)
}
return@use 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
}
/**
* Given a [FileHeader] and the both [RawText], generate a [List] of [Hunk]
*/
private fun format(head: FileHeader, oldRawText: RawText, newRawText: RawText): List<Hunk> {
return if (head.patchType == PatchType.UNIFIED)
format(head.toEditList(), oldRawText, newRawText)
class FormatHunksUseCase @Inject constructor() {
operator fun invoke(
fileHeader: FileHeader,
rawOld: RawText,
rawNew: RawText,
): List<Hunk> {
return if (fileHeader.patchType == PatchType.UNIFIED)
format(fileHeader.toEditList(), rawOld, rawNew)
else
emptyList()
}
@ -169,7 +84,9 @@ class HunkDiffGenerator @Inject constructor(
newCurrentLine++
}
if (end(curEdit, oldCurrentLine, newCurrentLine) && ++curIdx < edits.size) curEdit = edits[curIdx]
if (end(curEdit, oldCurrentLine, newCurrentLine) && ++curIdx < edits.size) {
curEdit = edits[curIdx]
}
}
hunksList.add(Hunk(headerText, lines))
@ -206,8 +123,10 @@ class HunkDiffGenerator @Inject constructor(
private fun findCombinedEnd(edits: List<Edit>, i: Int): Int {
var end = i + 1
while (end < edits.size
&& (combineA(edits, end) || combineB(edits, end))
while (
end < edits.size &&
(combineA(edits, end) || combineB(edits, end))
) end++
return end - 1
}
@ -225,22 +144,3 @@ class HunkDiffGenerator @Inject constructor(
}
}
sealed class DiffResult(
val diffEntry: DiffEntry,
) {
class Text(
diffEntry: DiffEntry,
val hunks: List<Hunk>
) : DiffResult(diffEntry)
class TextSplit(
diffEntry: DiffEntry,
val hunks: List<SplitHunk>
) : DiffResult(diffEntry)
class NonText(
diffEntry: DiffEntry,
val oldBinaryContent: EntryContent,
val newBinaryContent: EntryContent,
) : DiffResult(diffEntry)
}

View File

@ -0,0 +1,55 @@
package com.jetpackduba.gitnuro.git.diff
import com.jetpackduba.gitnuro.git.EntryContent
import com.jetpackduba.gitnuro.git.RawFileManager
import org.eclipse.jgit.diff.DiffEntry
import org.eclipse.jgit.diff.DiffFormatter
import org.eclipse.jgit.lib.Repository
import org.eclipse.jgit.treewalk.AbstractTreeIterator
import java.io.ByteArrayOutputStream
import java.io.InvalidObjectException
import javax.inject.Inject
class GetDiffContentUseCase @Inject constructor(
private val rawFileManager: RawFileManager,
) {
operator fun invoke(
repository: Repository,
diffEntry: DiffEntry,
oldTreeIterator: AbstractTreeIterator?,
newTreeIterator: AbstractTreeIterator?,
): DiffContent {
val outputStream = ByteArrayOutputStream() // Dummy output stream used for the diff formatter
outputStream.use {
val diffFormatter = DiffFormatter(outputStream).apply {
setRepository(repository)
}
if (oldTreeIterator != null && newTreeIterator != null) {
diffFormatter.scan(oldTreeIterator, newTreeIterator)
}
val fileHeader = diffFormatter.toFileHeader(diffEntry)
val rawOld = rawFileManager.getRawContent(
repository,
DiffEntry.Side.OLD,
diffEntry,
oldTreeIterator,
newTreeIterator
)
val rawNew = rawFileManager.getRawContent(
repository,
DiffEntry.Side.NEW,
diffEntry,
oldTreeIterator,
newTreeIterator
)
if (rawOld == EntryContent.InvalidObjectBlob || rawNew == EntryContent.InvalidObjectBlob)
throw InvalidObjectException("Invalid object in diff format")
return DiffContent(fileHeader, rawOld, rawNew)
}
}
}

View File

@ -30,6 +30,7 @@ private const val PREF_WINDOW_PLACEMENT = "windowsPlacement"
private const val PREF_CUSTOM_THEME = "customTheme"
private const val PREF_UI_SCALE = "ui_scale"
private const val PREF_DIFF_TYPE = "diffType"
private const val PREF_DIFF_FULL_FILE = "diffFullFile"
private const val PREF_SWAP_UNCOMMITED_CHANGES = "inverseUncommitedChanges"
@ -72,6 +73,9 @@ class AppSettings @Inject constructor() {
private val _textDiffTypeFlow = MutableStateFlow(textDiffType)
val textDiffTypeFlow = _textDiffTypeFlow.asStateFlow()
private val _textDiffFullFileFlow = MutableStateFlow(diffDisplayFullFile)
val diffDisplayFullFileFlow = _textDiffFullFileFlow.asStateFlow()
var latestTabsOpened: String
get() = preferences.get(PREF_LATEST_REPOSITORIES_TABS_OPENED, "")
set(value) {
@ -188,6 +192,16 @@ class AppSettings @Inject constructor() {
_textDiffTypeFlow.value = textDiffType
}
var diffDisplayFullFile: Boolean
get() {
return preferences.getBoolean(PREF_DIFF_FULL_FILE, false)
}
set(newValue) {
preferences.putBoolean(PREF_DIFF_TYPE, newValue)
_textDiffFullFileFlow.value = newValue
}
fun saveCustomTheme(filePath: String) {
val file = File(filePath)
val content = file.readText()

View File

@ -55,6 +55,7 @@ import com.jetpackduba.gitnuro.keybindings.matchesBinding
import com.jetpackduba.gitnuro.theme.*
import com.jetpackduba.gitnuro.ui.components.ScrollableLazyColumn
import com.jetpackduba.gitnuro.ui.components.SecondaryButton
import com.jetpackduba.gitnuro.ui.components.Tooltip
import com.jetpackduba.gitnuro.ui.context_menu.ContextMenu
import com.jetpackduba.gitnuro.ui.context_menu.ContextMenuElement
import com.jetpackduba.gitnuro.ui.context_menu.CustomTextContextMenu
@ -80,6 +81,7 @@ fun Diff(
) {
val diffResultState = diffViewModel.diffResult.collectAsState()
val diffType by diffViewModel.diffTypeFlow.collectAsState()
val isDisplayFullFile by diffViewModel.isDisplayFullFile.collectAsState()
val viewDiffResult = diffResultState.value ?: return
val focusRequester = remember { FocusRequester() }
@ -116,9 +118,11 @@ fun Diff(
diffEntry = diffEntry,
onCloseDiffView = onCloseDiffView,
diffType = diffType,
isDisplayFullFile = isDisplayFullFile,
onStageFile = { diffViewModel.stageFile(it) },
onUnstageFile = { diffViewModel.unstageFile(it) },
onChangeDiffType = { diffViewModel.changeTextDiffType(it) }
onChangeDiffType = { diffViewModel.changeTextDiffType(it) },
onDisplayFullFile = { diffViewModel.changeDisplayFullFile(it) },
)
val scrollState by diffViewModel.lazyListState.collectAsState()
@ -773,10 +777,12 @@ private fun DiffHeader(
diffEntryType: DiffEntryType,
diffEntry: DiffEntry,
diffType: TextDiffType,
isDisplayFullFile: Boolean,
onCloseDiffView: () -> Unit,
onStageFile: (StatusEntry) -> Unit,
onUnstageFile: (StatusEntry) -> Unit,
onChangeDiffType: (TextDiffType) -> Unit,
onDisplayFullFile: (Boolean) -> Unit,
) {
Row(
modifier = Modifier
@ -825,8 +831,15 @@ private fun DiffHeader(
Row(verticalAlignment = Alignment.CenterVertically) {
if (diffEntryType.statusType != StatusType.ADDED && diffEntryType.statusType != StatusType.REMOVED) {
DiffTypeButtons(diffType = diffType, onChangeDiffType = onChangeDiffType)
Row(verticalAlignment = Alignment.CenterVertically) {
if (diffEntryType.statusType != StatusType.ADDED && diffEntryType.statusType != StatusType.REMOVED) {
DiffTypeButtons(
diffType = diffType,
isDisplayFullFile = isDisplayFullFile,
onChangeDiffType = onChangeDiffType,
onDisplayFullFile = onDisplayFullFile,
)
}
}
if (diffEntryType is DiffEntryType.UncommitedDiff) {
@ -853,40 +866,114 @@ private fun DiffHeader(
}
@Composable
fun DiffTypeButtons(diffType: TextDiffType, onChangeDiffType: (TextDiffType) -> Unit) {
fun StateIcon(
icon: String,
tooltip: String,
isToggled: Boolean,
onClick: () -> Unit,
) {
Tooltip(tooltip) {
Box(
modifier = Modifier
.clip(RoundedCornerShape(4.dp))
.run {
if (isToggled)
this.background(MaterialTheme.colors.onSurface.copy(alpha = 0.2f))
else
this
}
.handMouseClickable { if(!isToggled) onClick() }
.padding(4.dp)
) {
Icon(
painterResource(icon),
contentDescription = null,
tint = MaterialTheme.colors.onSurface,
modifier = Modifier
.size(24.dp),
)
}
}
}
@Composable
fun DiffTypeButtons(
diffType: TextDiffType,
isDisplayFullFile: Boolean,
onChangeDiffType: (TextDiffType) -> Unit,
onDisplayFullFile: (Boolean) -> Unit
) {
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.padding(horizontal = 16.dp)
) {
Text(
"Unified",
color = MaterialTheme.colors.onBackground,
style = MaterialTheme.typography.caption,
)
Switch(
checked = diffType == TextDiffType.SPLIT,
onCheckedChange = { checked ->
val newType = if (checked)
TextDiffType.SPLIT
else
TextDiffType.UNIFIED
onChangeDiffType(newType)
},
colors = SwitchDefaults.colors(
uncheckedThumbColor = MaterialTheme.colors.secondaryVariant,
uncheckedTrackColor = MaterialTheme.colors.secondaryVariant,
uncheckedTrackAlpha = 0.54f
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.padding(end = 16.dp)
) {
StateIcon(
icon = AppIcons.HORIZONTAL_SPLIT,
tooltip = "Divide by hunks",
isToggled = !isDisplayFullFile,
onClick = { onDisplayFullFile(false) },
)
)
Text(
"Split",
color = MaterialTheme.colors.onBackground,
// modifier = Modifier.padding(horizontal = 4.dp),
style = MaterialTheme.typography.caption,
)
StateIcon(
icon = AppIcons.DESCRIPTION,
tooltip = "View the complete file",
isToggled = isDisplayFullFile,
onClick = { onDisplayFullFile(true) },
)
}
Row(
verticalAlignment = Alignment.CenterVertically,
) {
StateIcon(
icon = AppIcons.UNIFIED,
tooltip = "Unified diff",
isToggled = diffType == TextDiffType.UNIFIED,
onClick = { onChangeDiffType(TextDiffType.UNIFIED) },
)
StateIcon(
icon = AppIcons.VERTICAL_SPLIT,
tooltip = "Split diff",
isToggled = diffType == TextDiffType.SPLIT,
onClick = { onChangeDiffType(TextDiffType.SPLIT) },
)
}
//
// Text(
// "Unified",
// color = MaterialTheme.colors.onBackground,
// style = MaterialTheme.typography.caption,
// )
//
// Switch(
// checked = diffType == TextDiffType.SPLIT,
// onCheckedChange = { checked ->
// val newType = if (checked)
// TextDiffType.SPLIT
// else
// TextDiffType.UNIFIED
//
// onChangeDiffType(newType)
// },
// colors = SwitchDefaults.colors(
// uncheckedThumbColor = MaterialTheme.colors.secondaryVariant,
// uncheckedTrackColor = MaterialTheme.colors.secondaryVariant,
// uncheckedTrackAlpha = 0.54f
// )
// )
//
// Text(
// "Split",
// color = MaterialTheme.colors.onBackground,
//// modifier = Modifier.padding(horizontal = 4.dp),
// style = MaterialTheme.typography.caption,
// )
}
}

View File

@ -15,6 +15,7 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.drop
import kotlinx.coroutines.launch
import org.eclipse.jgit.diff.DiffEntry
import javax.inject.Inject
@ -41,20 +42,31 @@ class DiffViewModel @Inject constructor(
val diffResult: StateFlow<ViewDiffResult?> = _diffResult
val diffTypeFlow = settings.textDiffTypeFlow
private var diffEntryType: DiffEntryType? = null
private var diffTypeFlowChangesCount = 0
val isDisplayFullFile = settings.diffDisplayFullFileFlow
private var diffEntryType: DiffEntryType? = null
private var diffJob: Job? = null
init {
tabScope.launch {
diffTypeFlow.collect {
diffTypeFlow
.drop(1) // Ignore the first time the flow triggers, we only care about updates
.collect {
val diffEntryType = this@DiffViewModel.diffEntryType
if (diffTypeFlowChangesCount > 0 && diffEntryType != null) { // Ignore the first time the flow triggers, we only care about updates
if (diffEntryType != null) {
updateDiff(diffEntryType)
}
}
}
diffTypeFlowChangesCount++
tabScope.launch {
isDisplayFullFile
.drop(1) // Ignore the first time the flow triggers, we only care about updates
.collect {
val diffEntryType = this@DiffViewModel.diffEntryType
if (diffEntryType != null) {
updateDiff(diffEntryType)
}
}
}
@ -110,7 +122,7 @@ class DiffViewModel @Inject constructor(
delayMs = if (isFirstLoad) 0 else DIFF_MIN_TIME_IN_MS_TO_SHOW_LOAD,
onDelayTriggered = { _diffResult.value = ViewDiffResult.Loading(diffEntryType.filePath) }
) {
val diffFormat = formatDiffUseCase(git, diffEntryType)
val diffFormat = formatDiffUseCase(git, diffEntryType, isDisplayFullFile.value)
val diffEntry = diffFormat.diffEntry
if (
diffTypeFlow.value == TextDiffType.SPLIT &&
@ -178,6 +190,10 @@ class DiffViewModel @Inject constructor(
settings.textDiffType = newDiffType
}
fun changeDisplayFullFile(isDisplayFullFile: Boolean) {
settings.diffDisplayFullFile = isDisplayFullFile
}
fun stageHunkLine(entry: DiffEntry, hunk: Hunk, line: Line) = tabState.runOperation(
refreshType = RefreshType.UNCOMMITED_CHANGES,
showError = true,

View File

@ -108,7 +108,7 @@ class HistoryViewModel @Inject constructor(
val diffEntryType = DiffEntryType.CommitDiff(diffEntry)
val diffResult = formatDiffUseCase(git, diffEntryType)
val diffResult = formatDiffUseCase(git, diffEntryType, false) // TODO This hardcoded false should be changed when the UI is implemented
val textDiffType = settings.textDiffType
val formattedDiffResult = if (textDiffType == TextDiffType.SPLIT && diffResult is DiffResult.Text) {

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"><path d="M0 0h24v24H0V0z" fill="none"/><path d="M8 16h8v2H8zm0-4h8v2H8zm6-10H6c-1.1 0-2 .9-2 2v16c0 1.1.89 2 1.99 2H18c1.1 0 2-.9 2-2V8l-6-6zm4 18H6V4h7v5h5v11z"/></svg>

After

Width:  |  Height:  |  Size: 271 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"><path d="M0 0h24v24H0V0z" fill="none"/><path d="M19 15v2H5v-2h14m2-10H3v2h18V5zm0 4H3v2h18V9zm0 4H3v6h18v-6z"/></svg>

After

Width:  |  Height:  |  Size: 219 B

View File

@ -0,0 +1,10 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_402_2142)">
<path d="M4 13H20V15H4V13ZM4 17H20V19H4V17ZM4 9H20V11H4V9ZM4 5H20V7H4V5Z" fill="black"/>
</g>
<defs>
<clipPath id="clip0_402_2142">
<rect width="24" height="24" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 336 B

View File

@ -0,0 +1,11 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_402_2113)">
<path d="M3 13H11V15H3V13ZM3 17H11V19H3V17ZM3 9H11V11H3V9ZM3 5H11V7H3V5Z" fill="black"/>
<path d="M13 13H21V15H13V13ZM13 17H21V19H13V17ZM13 9H21V11H13V9ZM13 5H21V7H13V5Z" fill="black"/>
</g>
<defs>
<clipPath id="clip0_402_2113">
<rect width="24" height="24" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 433 B