Added lines numbers in diff view

This commit is contained in:
Abdelilah El Aissaoui 2022-01-05 02:52:32 +01:00
parent f20cfbb698
commit 1ed6572170
2 changed files with 196 additions and 100 deletions

View File

@ -2,7 +2,11 @@ package app.git.diff
data class Hunk(val header: String, val lines: List<Line>)
data class Line(val text: String, val oldLineNumber: Int, val newLineNumber: Int, val lineType: LineType)
data class Line(val text: String, val oldLineNumber: Int, val newLineNumber: Int, val lineType: LineType) {
// lines numbers are stored based on 0 being the first one but on a file the first line is the 1, so increment it!
val displayOldLineNumber: Int = oldLineNumber + 1
val displayNewLineNumber: Int = newLineNumber + 1
}
enum class LineType {
CONTEXT,

View File

@ -2,7 +2,8 @@ package app.ui
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.text.selection.DisableSelection
import androidx.compose.foundation.text.selection.SelectionContainer
import androidx.compose.material.ButtonDefaults
import androidx.compose.material.MaterialTheme
@ -16,12 +17,15 @@ 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.diff.Hunk
import app.git.diff.Line
import app.git.diff.LineType
import app.theme.primaryTextColor
import app.ui.components.ScrollableLazyColumn
import app.ui.components.SecondaryButton
import app.viewmodels.DiffViewModel
import org.eclipse.jgit.diff.DiffEntry
import kotlin.math.max
@Composable
fun Diff(
@ -41,36 +45,7 @@ fun Diff(
.background(MaterialTheme.colors.background)
.fillMaxSize()
) {
Row(
modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically,
) {
val filePath = if(diffEntry.newPath != "/dev/null")
diffEntry.newPath
else
diffEntry.oldPath
Text(
text = filePath,
color = MaterialTheme.colors.primaryTextColor,
fontSize = 16.sp,
modifier = Modifier.padding(horizontal = 16.dp),
)
Spacer(modifier = Modifier.weight(1f))
OutlinedButton(
modifier = Modifier
.padding(vertical = 8.dp, horizontal = 16.dp),
onClick = onCloseDiffView,
colors = ButtonDefaults.buttonColors(
backgroundColor = MaterialTheme.colors.background,
contentColor = MaterialTheme.colors.primary,
)
) {
Text("Close diff")
}
}
DiffHeader(diffEntry, onCloseDiffView)
val scrollState by diffViewModel.lazyListState.collectAsState()
ScrollableLazyColumn(
@ -78,78 +53,23 @@ fun Diff(
.fillMaxSize(),
state = scrollState
) {
itemsIndexed(hunks) { index, hunk ->
val hunksSeparation = if (index == 0)
0.dp
else
16.dp
Row(
modifier = Modifier
.padding(top = hunksSeparation)
.background(MaterialTheme.colors.surface)
.padding(vertical = 4.dp)
.fillMaxWidth()
) {
Text(
text = hunk.header,
color = MaterialTheme.colors.primaryTextColor,
)
Spacer(modifier = Modifier.weight(1f))
if (
(diffEntryType is DiffEntryType.StagedDiff || diffEntryType is DiffEntryType.UnstagedDiff) &&
diffEntryType.diffEntry.changeType == DiffEntry.ChangeType.MODIFY
) {
val buttonText: String
val color: Color
if (diffEntryType is DiffEntryType.StagedDiff) {
buttonText = "Unstage hunk"
color = MaterialTheme.colors.error
} else {
buttonText = "Stage hunk"
color = MaterialTheme.colors.primary
}
SecondaryButton(
text = buttonText,
backgroundButton = color,
onClick = {
if (diffEntryType is DiffEntryType.StagedDiff) {
diffViewModel.unstageHunk(diffEntryType.diffEntry, hunk)
} else {
diffViewModel.stageHunk(diffEntryType.diffEntry, hunk)
}
}
)
}
}
item { Spacer(modifier = Modifier.height(16.dp)) }
items(hunks) { hunk ->
HunkHeader(
hunk = hunk,
diffEntryType = diffEntryType,
diffViewModel = diffViewModel,
)
SelectionContainer {
Column {
hunk.lines.forEach { line ->
val backgroundColor = when (line.lineType) {
LineType.ADDED -> {
Color(0x77a9d49b)
}
LineType.REMOVED -> {
Color(0x77dea2a2)
}
LineType.CONTEXT -> {
MaterialTheme.colors.background
}
}
val oldHighestLineNumber = hunk.lines.maxOf { it.displayOldLineNumber }
val newHighestLineNumber = hunk.lines.maxOf { it.displayNewLineNumber }
val highestLineNumber = max(oldHighestLineNumber, newHighestLineNumber)
val highestLineNumberLength = highestLineNumber.toString().count()
Text(
text = line.text,
modifier = Modifier
.background(backgroundColor)
.padding(start = 16.dp)
.fillMaxWidth(),
color = MaterialTheme.colors.primaryTextColor,
maxLines = 1,
fontFamily = FontFamily.Monospace,
fontSize = 14.sp,
)
hunk.lines.forEach { line ->
DiffLine(highestLineNumberLength, line)
}
}
}
@ -158,3 +78,175 @@ fun Diff(
}
}
@Composable
fun HunkHeader(
hunk: Hunk,
diffEntryType: DiffEntryType,
diffViewModel: DiffViewModel,
) {
Row(
modifier = Modifier
.background(MaterialTheme.colors.surface)
.padding(vertical = 4.dp)
.fillMaxWidth()
) {
Text(
text = hunk.header,
color = MaterialTheme.colors.primaryTextColor,
)
Spacer(modifier = Modifier.weight(1f))
if (
(diffEntryType is DiffEntryType.StagedDiff || diffEntryType is DiffEntryType.UnstagedDiff) &&
diffEntryType.diffEntry.changeType == DiffEntry.ChangeType.MODIFY
) {
val buttonText: String
val color: Color
if (diffEntryType is DiffEntryType.StagedDiff) {
buttonText = "Unstage hunk"
color = MaterialTheme.colors.error
} else {
buttonText = "Stage hunk"
color = MaterialTheme.colors.primary
}
SecondaryButton(
text = buttonText,
backgroundButton = color,
onClick = {
if (diffEntryType is DiffEntryType.StagedDiff) {
diffViewModel.unstageHunk(diffEntryType.diffEntry, hunk)
} else {
diffViewModel.stageHunk(diffEntryType.diffEntry, hunk)
}
}
)
}
}
}
@Composable
fun DiffHeader(diffEntry: DiffEntry, onCloseDiffView: () -> Unit) {
Row(
modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically,
) {
val filePath = if(diffEntry.newPath != "/dev/null")
diffEntry.newPath
else
diffEntry.oldPath
Text(
text = filePath,
color = MaterialTheme.colors.primaryTextColor,
fontSize = 16.sp,
modifier = Modifier.padding(horizontal = 16.dp),
)
Spacer(modifier = Modifier.weight(1f))
OutlinedButton(
modifier = Modifier
.padding(vertical = 8.dp, horizontal = 16.dp),
onClick = onCloseDiffView,
colors = ButtonDefaults.buttonColors(
backgroundColor = MaterialTheme.colors.background,
contentColor = MaterialTheme.colors.primary,
)
) {
Text("Close diff")
}
}
}
@Composable
fun DiffLine(highestLineNumberLength: Int, line: Line) {
val backgroundColor = when (line.lineType) {
LineType.ADDED -> {
Color(0x77a9d49b)
}
LineType.REMOVED -> {
Color(0x77dea2a2)
}
LineType.CONTEXT -> {
MaterialTheme.colors.background
}
}
Row (
modifier = Modifier
.background(backgroundColor)
) {
val oldLineText = if(line.lineType == LineType.REMOVED || line.lineType == LineType.CONTEXT) {
formattedLineNumber(line.displayOldLineNumber, highestLineNumberLength)
} else
emptyLineNumber(highestLineNumberLength)
val newLineText = if(line.lineType == LineType.ADDED || line.lineType == LineType.CONTEXT) {
formattedLineNumber(line.displayNewLineNumber, highestLineNumberLength)
} else
emptyLineNumber(highestLineNumberLength)
DisableSelection {
LineNumber(
text = oldLineText,
)
LineNumber(
text = newLineText
)
}
Text(
text = line.text,
modifier = Modifier
.padding(start = 8.dp)
.fillMaxWidth(),
color = MaterialTheme.colors.primaryTextColor,
maxLines = 1,
fontFamily = FontFamily.Monospace,
fontSize = 14.sp,
)
}
}
@Composable
fun LineNumber(text: String) {
Text(
text = text,
color = MaterialTheme.colors.primaryTextColor,
modifier = Modifier
.background(MaterialTheme.colors.surface)
.padding(horizontal = 4.dp),
fontFamily = FontFamily.Monospace,
fontSize = 14.sp,
)
}
fun formattedLineNumber(number: Int, charactersCount: Int): String {
val numberStr = number.toString()
return if(numberStr.count() == charactersCount)
numberStr
else {
val lengthDiff = charactersCount - numberStr.count()
val numberBuilder = StringBuilder()
// Add whitespaces before the numbers
repeat(lengthDiff) {
numberBuilder.append(" ")
}
numberBuilder.append(numberStr)
numberBuilder.toString()
}
}
fun emptyLineNumber(charactersCount: Int): String {
val numberBuilder = StringBuilder()
// Add whitespaces before the numbers
repeat(charactersCount) {
numberBuilder.append(" ")
}
return numberBuilder.toString()
}