diff --git a/src/main/kotlin/app/ui/FileHistory.kt b/src/main/kotlin/app/ui/FileHistory.kt index deb630f..4862342 100644 --- a/src/main/kotlin/app/ui/FileHistory.kt +++ b/src/main/kotlin/app/ui/FileHistory.kt @@ -34,6 +34,8 @@ import app.theme.secondaryTextColor import app.ui.components.AvatarImage import app.ui.components.ScrollableLazyColumn import app.ui.components.TooltipText +import app.ui.diff.HunkSplitTextDiff +import app.ui.diff.HunkUnifiedTextDiff import app.viewmodels.HistoryState import app.viewmodels.HistoryViewModel import app.viewmodels.ViewDiffResult diff --git a/src/main/kotlin/app/ui/RepositoryOpen.kt b/src/main/kotlin/app/ui/RepositoryOpen.kt index 8a2a9cc..e0d74ba 100644 --- a/src/main/kotlin/app/ui/RepositoryOpen.kt +++ b/src/main/kotlin/app/ui/RepositoryOpen.kt @@ -9,7 +9,6 @@ import androidx.compose.material.MaterialTheme import androidx.compose.material.Text import androidx.compose.runtime.* import androidx.compose.ui.Alignment -import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.focusRequester @@ -26,6 +25,7 @@ import app.ui.components.ScrollableColumn import app.ui.dialogs.AuthorDialog import app.ui.dialogs.NewBranchDialog import app.ui.dialogs.StashWithMessageDialog +import app.ui.diff.Diff import app.ui.log.Log import app.viewmodels.BlameState import app.viewmodels.TabViewModel diff --git a/src/main/kotlin/app/ui/diff/Diff.kt b/src/main/kotlin/app/ui/diff/Diff.kt index d3433b6..ac5c51d 100644 --- a/src/main/kotlin/app/ui/diff/Diff.kt +++ b/src/main/kotlin/app/ui/diff/Diff.kt @@ -1,6 +1,6 @@ @file:OptIn(ExperimentalComposeUiApi::class) -package app.ui +package app.ui.diff import androidx.compose.foundation.Image import androidx.compose.foundation.background @@ -20,7 +20,9 @@ import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.ColorFilter import androidx.compose.ui.input.key.onPreviewKeyEvent +import androidx.compose.ui.input.pointer.PointerEventType import androidx.compose.ui.input.pointer.PointerIconDefaults +import androidx.compose.ui.input.pointer.onPointerEvent import androidx.compose.ui.input.pointer.pointerHoverIcon import androidx.compose.ui.res.loadImageBitmap import androidx.compose.ui.res.painterResource @@ -306,6 +308,7 @@ fun HunkSplitTextDiff( onResetHunk: (DiffEntry, Hunk) -> Unit, ) { val hunks = diffResult.hunks + var selectableSide by remember { mutableStateOf(SelectableSide.BOTH) } SelectionContainer { ScrollableLazyColumn( @@ -332,28 +335,55 @@ fun HunkSplitTextDiff( val highestLineNumberLength = highestLineNumber.toString().count() items(splitHunk.lines) { linesPair -> - SplitDiffLine(highestLineNumberLength, linesPair.first, linesPair.second) + SplitDiffLine( + highestLineNumberLength = highestLineNumberLength, + oldLine = linesPair.first, + newLine = linesPair.second, + selectableSide = selectableSide, + onChangeSelectableSide = { newSelectableSide -> + if (newSelectableSide != selectableSide) { + println("newSelectableSide $newSelectableSide") + selectableSide = newSelectableSide + } + } + ) } } } } - } @Composable -fun SplitDiffLine(highestLineNumberLength: Int, first: Line?, second: Line?) { +fun DynamicSelectionDisable(isDisabled: Boolean, content: @Composable () -> Unit) { + if (isDisabled) { + DisableSelection(content) + } else + content() +} + +@Composable +fun SplitDiffLine( + highestLineNumberLength: Int, + oldLine: Line?, + newLine: Line?, + selectableSide: SelectableSide, + onChangeSelectableSide: (SelectableSide) -> Unit, +) { Row( modifier = Modifier .background(MaterialTheme.colors.secondarySurface) .height(IntrinsicSize.Min) ) { - Box( + SplitDiffLineSide( modifier = Modifier - .weight(1f) - ) { - if (first != null) - SplitDiffLine(highestLineNumberLength, first, first.oldLineNumber + 1) - } + .weight(1f), + highestLineNumberLength = highestLineNumberLength, + line = oldLine, + displayLineNumber = oldLine?.displayOldLineNumber ?: 0, + currentSelectableSide = selectableSide, + lineSelectableSide = SelectableSide.OLD, + onChangeSelectableSide = onChangeSelectableSide, + ) Box( modifier = Modifier @@ -362,13 +392,57 @@ fun SplitDiffLine(highestLineNumberLength: Int, first: Line?, second: Line?) { .background(MaterialTheme.colors.secondarySurface) ) - Box(modifier = Modifier.weight(1f)) { - if (second != null) - SplitDiffLine(highestLineNumberLength, second, second.newLineNumber + 1) + SplitDiffLineSide( + modifier = Modifier + .weight(1f), + highestLineNumberLength = highestLineNumberLength, + line = newLine, + displayLineNumber = newLine?.displayNewLineNumber ?: 0, + currentSelectableSide = selectableSide, + lineSelectableSide = SelectableSide.NEW, + onChangeSelectableSide = onChangeSelectableSide, + ) + + } +} + +@Composable +fun SplitDiffLineSide( + modifier: Modifier, + highestLineNumberLength: Int, + line: Line?, + displayLineNumber: Int, + currentSelectableSide: SelectableSide, + lineSelectableSide: SelectableSide, + onChangeSelectableSide: (SelectableSide) -> Unit, +) { + Box( + modifier = modifier + .onPointerEvent(PointerEventType.Press) { + onChangeSelectableSide(lineSelectableSide) + } + .onPointerEvent(PointerEventType.Release) { + onChangeSelectableSide(SelectableSide.BOTH) + } + ) { + if (line != null) { + // To avoid both sides being selected, disable one side when the use is interacting with the other + DynamicSelectionDisable( + currentSelectableSide != lineSelectableSide && + currentSelectableSide != SelectableSide.BOTH + ) { + SplitDiffLine(highestLineNumberLength, line, displayLineNumber) + } } } } +enum class SelectableSide { + BOTH, + OLD, + NEW; +} + @Composable fun HunkHeader( header: String,