Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions AGENT.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# DiffPlex Development Guide

## Build/Test Commands
- Build: `dotnet build`
- Test all: `dotnet test`
- Test single project: `dotnet test Facts.DiffPlex`
- Test specific class: `dotnet test Facts.DiffPlex --filter "FullyQualifiedName~InlineDiffBuilderFacts"`
- Test specific method: `dotnet test --filter "FullyQualifiedName~ClassName.MethodName"`
- Restore packages: `dotnet restore`

## Code Style Guidelines
- **Classes/Interfaces**: PascalCase (interfaces with "I" prefix)
- **Methods/Properties**: PascalCase
- **Fields/Parameters/Variables**: camelCase
- **Constants**: PascalCase
- Use expression-bodied members for simple getters
- Modern C# collection initialization: `data.Pieces = [];`
- Null checks with `ArgumentNullException` for public APIs
- Singleton pattern with static `Instance` properties

## Test Conventions
- Test classes: `*Facts` suffix (e.g., `DifferFacts`)
- Use xUnit with `[Fact]` attributes
- Nested classes to organize related tests
- Descriptive test names: `Will_throw_if_parameter_is_null`
- Use Moq for mocking dependencies

## Error Handling
- Throw `ArgumentNullException` for null parameters in public APIs
- Use descriptive parameter names in exceptions
21 changes: 21 additions & 0 deletions DiffPlex.Blazor/Components/App.razor
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<base href="/" />
<link rel="stylesheet" href="@Assets["lib/bootstrap/dist/css/bootstrap.min.css"]" />
<link rel="stylesheet" href="@Assets["app.css"]" />
<link rel="stylesheet" href="@Assets["DiffPlex.Blazor.styles.css"]" />
<ImportMap />
<link rel="icon" type="image/png" href="favicon.png" />
<HeadOutlet />
</head>

<body>
<Routes />
<script src="_framework/blazor.web.js"></script>
</body>

</html>
169 changes: 169 additions & 0 deletions DiffPlex.Blazor/Components/DiffViewer.razor
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
@using DiffPlex
@using DiffPlex.DiffBuilder
@using DiffPlex.DiffBuilder.Model

<div class="diff-viewer">
<div class="diff-header">
<div class="left-header">
<h5>@OldTextHeader</h5>
</div>
<div class="right-header">
<h5>@NewTextHeader</h5>
</div>
</div>

<div class="diff-content">
@if (_diffModel != null)
{
<div class="left-panel">
@foreach (var line in _diffModel.OldText.Lines)
{
<div class="line @GetLineClass(line.Type)">
<span class="line-number">@(line.Position?.ToString() ?? "")</span>
<span class="line-content">@line.Text</span>
</div>
}
</div>

<div class="right-panel">
@foreach (var line in _diffModel.NewText.Lines)
{
<div class="line @GetLineClass(line.Type)">
<span class="line-number">@(line.Position?.ToString() ?? "")</span>
<span class="line-content">@line.Text</span>
</div>
}
</div>
}
</div>
</div>

@code {
[Parameter] public string OldText { get; set; } = "";
[Parameter] public string NewText { get; set; } = "";
[Parameter] public string OldTextHeader { get; set; } = "Original";
[Parameter] public string NewTextHeader { get; set; } = "Modified";
[Parameter] public bool IgnoreWhiteSpace { get; set; } = true;

private SideBySideDiffModel? _diffModel;

protected override void OnParametersSet()
{
UpdateDiff();
}

private void UpdateDiff()
{
if (!string.IsNullOrEmpty(OldText) || !string.IsNullOrEmpty(NewText))
{
var differ = new SideBySideDiffBuilder(new Differ());
_diffModel = differ.BuildDiffModel(OldText ?? "", NewText ?? "", IgnoreWhiteSpace);
}
}

private string GetLineClass(ChangeType changeType)
{
return changeType switch
{
ChangeType.Inserted => "inserted",
ChangeType.Deleted => "deleted",
ChangeType.Modified => "modified",
_ => "unchanged"
};
}
}

<style>
.diff-viewer {
border: 1px solid #d0d7de;
border-radius: 6px;
overflow: hidden;
font-family: 'Consolas', 'Monaco', 'Courier New', monospace;
font-size: 12px;
}

.diff-header {
display: flex;
background-color: #f6f8fa;
border-bottom: 1px solid #d0d7de;
}

.left-header, .right-header {
flex: 1;
padding: 8px 12px;
font-weight: 600;
color: #24292f;
}

.left-header {
border-right: 1px solid #d0d7de;
}

.diff-content {
display: flex;
max-height: 500px;
overflow: auto;
}

.left-panel, .right-panel {
flex: 1;
min-width: 0;
}

.left-panel {
border-right: 1px solid #d0d7de;
}

.line {
display: flex;
min-height: 20px;
line-height: 20px;
white-space: nowrap;
}

.line-number {
width: 50px;
padding: 0 8px;
text-align: right;
color: #656d76;
background-color: #f6f8fa;
border-right: 1px solid #d0d7de;
user-select: none;
flex-shrink: 0;
}

.line-content {
padding: 0 8px;
white-space: pre;
overflow-x: auto;
flex: 1;
}

.line.inserted {
background-color: #d1f4d0;
}

.line.inserted .line-number {
background-color: #a7f3d0;
}

.line.deleted {
background-color: #ffeef0;
}

.line.deleted .line-number {
background-color: #fecdd3;
}

.line.modified {
background-color: #fff8dc;
}

.line.modified .line-number {
background-color: #fef3c7;
}

.line.unchanged {
background-color: #ffffff;
}
</style>
168 changes: 168 additions & 0 deletions DiffPlex.Blazor/Components/InlineDiffViewer.razor
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
@using DiffPlex
@using DiffPlex.DiffBuilder
@using DiffPlex.DiffBuilder.Model

<div class="inline-diff-viewer">
<div class="diff-header">
<h5>@Header</h5>
</div>

<div class="diff-content">
@if (_diffModel != null)
{
@foreach (var line in _diffModel.Lines)
{
<div class="line @GetLineClass(line.Type)">
<span class="line-number">@(line.Position?.ToString() ?? "")</span>
<span class="change-type">@GetChangeSymbol(line.Type)</span>
<span class="line-content">@line.Text</span>
</div>
}
}
</div>
</div>

@code {
[Parameter] public string OldText { get; set; } = "";
[Parameter] public string NewText { get; set; } = "";
[Parameter] public string Header { get; set; } = "Diff";
[Parameter] public bool IgnoreWhiteSpace { get; set; } = true;

private DiffPaneModel? _diffModel;

protected override void OnParametersSet()
{
UpdateDiff();
}

private void UpdateDiff()
{
if (!string.IsNullOrEmpty(OldText) || !string.IsNullOrEmpty(NewText))
{
var differ = new InlineDiffBuilder(new Differ());
_diffModel = differ.BuildDiffModel(OldText ?? "", NewText ?? "", IgnoreWhiteSpace);
}
}

private string GetLineClass(ChangeType changeType)
{
return changeType switch
{
ChangeType.Inserted => "inserted",
ChangeType.Deleted => "deleted",
ChangeType.Modified => "modified",
_ => "unchanged"
};
}

private string GetChangeSymbol(ChangeType changeType)
{
return changeType switch
{
ChangeType.Inserted => "+",
ChangeType.Deleted => "-",
ChangeType.Modified => "~",
_ => " "
};
}
}

<style>
.inline-diff-viewer {
border: 1px solid #d0d7de;
border-radius: 6px;
overflow: hidden;
font-family: 'Consolas', 'Monaco', 'Courier New', monospace;
font-size: 12px;
}

.diff-header {
background-color: #f6f8fa;
border-bottom: 1px solid #d0d7de;
padding: 8px 12px;
}

.diff-header h5 {
margin: 0;
font-weight: 600;
color: #24292f;
}

.diff-content {
max-height: 500px;
overflow: auto;
}

.line {
display: flex;
min-height: 20px;
line-height: 20px;
white-space: nowrap;
}

.line-number {
width: 50px;
padding: 0 8px;
text-align: right;
color: #656d76;
background-color: #f6f8fa;
border-right: 1px solid #d0d7de;
user-select: none;
flex-shrink: 0;
}

.change-type {
width: 20px;
padding: 0 4px;
text-align: center;
font-weight: bold;
border-right: 1px solid #d0d7de;
user-select: none;
flex-shrink: 0;
}

.line-content {
padding: 0 8px;
white-space: pre;
overflow-x: auto;
flex: 1;
}

.line.inserted {
background-color: #d1f4d0;
}

.line.inserted .line-number,
.line.inserted .change-type {
background-color: #a7f3d0;
color: #0d7377;
}

.line.deleted {
background-color: #ffeef0;
}

.line.deleted .line-number,
.line.deleted .change-type {
background-color: #fecdd3;
color: #d1242f;
}

.line.modified {
background-color: #fff8dc;
}

.line.modified .line-number,
.line.modified .change-type {
background-color: #fef3c7;
color: #b45309;
}

.line.unchanged {
background-color: #ffffff;
}

.line.unchanged .change-type {
background-color: #f6f8fa;
}
</style>
Loading