Added lines numbers in diff view
This commit is contained in:
parent
f20cfbb698
commit
1ed6572170
@ -2,7 +2,11 @@ package app.git.diff
|
|||||||
|
|
||||||
data class Hunk(val header: String, val lines: List<Line>)
|
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 {
|
enum class LineType {
|
||||||
CONTEXT,
|
CONTEXT,
|
||||||
|
@ -2,7 +2,8 @@ package app.ui
|
|||||||
|
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.layout.*
|
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.foundation.text.selection.SelectionContainer
|
||||||
import androidx.compose.material.ButtonDefaults
|
import androidx.compose.material.ButtonDefaults
|
||||||
import androidx.compose.material.MaterialTheme
|
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.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import app.git.DiffEntryType
|
import app.git.DiffEntryType
|
||||||
|
import app.git.diff.Hunk
|
||||||
|
import app.git.diff.Line
|
||||||
import app.git.diff.LineType
|
import app.git.diff.LineType
|
||||||
import app.theme.primaryTextColor
|
import app.theme.primaryTextColor
|
||||||
import app.ui.components.ScrollableLazyColumn
|
import app.ui.components.ScrollableLazyColumn
|
||||||
import app.ui.components.SecondaryButton
|
import app.ui.components.SecondaryButton
|
||||||
import app.viewmodels.DiffViewModel
|
import app.viewmodels.DiffViewModel
|
||||||
import org.eclipse.jgit.diff.DiffEntry
|
import org.eclipse.jgit.diff.DiffEntry
|
||||||
|
import kotlin.math.max
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun Diff(
|
fun Diff(
|
||||||
@ -41,36 +45,7 @@ fun Diff(
|
|||||||
.background(MaterialTheme.colors.background)
|
.background(MaterialTheme.colors.background)
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
) {
|
) {
|
||||||
Row(
|
DiffHeader(diffEntry, onCloseDiffView)
|
||||||
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")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val scrollState by diffViewModel.lazyListState.collectAsState()
|
val scrollState by diffViewModel.lazyListState.collectAsState()
|
||||||
ScrollableLazyColumn(
|
ScrollableLazyColumn(
|
||||||
@ -78,78 +53,23 @@ fun Diff(
|
|||||||
.fillMaxSize(),
|
.fillMaxSize(),
|
||||||
state = scrollState
|
state = scrollState
|
||||||
) {
|
) {
|
||||||
itemsIndexed(hunks) { index, hunk ->
|
item { Spacer(modifier = Modifier.height(16.dp)) }
|
||||||
val hunksSeparation = if (index == 0)
|
items(hunks) { hunk ->
|
||||||
0.dp
|
HunkHeader(
|
||||||
else
|
hunk = hunk,
|
||||||
16.dp
|
diffEntryType = diffEntryType,
|
||||||
Row(
|
diffViewModel = diffViewModel,
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SelectionContainer {
|
SelectionContainer {
|
||||||
Column {
|
Column {
|
||||||
hunk.lines.forEach { line ->
|
val oldHighestLineNumber = hunk.lines.maxOf { it.displayOldLineNumber }
|
||||||
val backgroundColor = when (line.lineType) {
|
val newHighestLineNumber = hunk.lines.maxOf { it.displayNewLineNumber }
|
||||||
LineType.ADDED -> {
|
val highestLineNumber = max(oldHighestLineNumber, newHighestLineNumber)
|
||||||
Color(0x77a9d49b)
|
val highestLineNumberLength = highestLineNumber.toString().count()
|
||||||
}
|
|
||||||
LineType.REMOVED -> {
|
|
||||||
Color(0x77dea2a2)
|
|
||||||
}
|
|
||||||
LineType.CONTEXT -> {
|
|
||||||
MaterialTheme.colors.background
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Text(
|
hunk.lines.forEach { line ->
|
||||||
text = line.text,
|
DiffLine(highestLineNumberLength, line)
|
||||||
modifier = Modifier
|
|
||||||
.background(backgroundColor)
|
|
||||||
.padding(start = 16.dp)
|
|
||||||
.fillMaxWidth(),
|
|
||||||
color = MaterialTheme.colors.primaryTextColor,
|
|
||||||
maxLines = 1,
|
|
||||||
fontFamily = FontFamily.Monospace,
|
|
||||||
fontSize = 14.sp,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -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()
|
||||||
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user