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
1 change: 1 addition & 0 deletions Assets/Tests/Editor/PausePointTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -784,6 +784,7 @@ public async Task Enable_WhenFileAndLineResolveToRealMethod_PatchesAndCapturesVa
Assert.That(response.Success, Is.True);
Assert.That(response.Id, Is.EqualTo($"{FixtureFilePath}:{FixtureLine}"));
Assert.That(response.ResolvedLine, Is.EqualTo(FixtureLine));
Assert.That(response.ResolvedLineText, Is.EqualTo("return sum;"));
Assert.That(response.ResolvedMethod, Does.Contain("Add"));

EnableBySourceLocationFixture fixture = new();
Expand Down
66 changes: 66 additions & 0 deletions Assets/Tests/Editor/SourcePausePointSourceLineReaderTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
using System.IO;

using NUnit.Framework;

using io.github.hatayama.UnityCliLoop.FirstPartyTools;

namespace io.github.hatayama.UnityCliLoop.Tests.Editor
{
/// <summary>
/// Verifies SourcePausePointSourceLineReader reads the trimmed text of a specific source
/// line from disk, and degrades to an empty string for missing files or out-of-range lines.
/// </summary>
[TestFixture]
public sealed class SourcePausePointSourceLineReaderTests
{
private string _tempFilePath;

[SetUp]
public void SetUp()
{
_tempFilePath = Path.GetTempFileName();
File.WriteAllLines(_tempFilePath, new[] { "line one", " line two ", "line three" });
}

[TearDown]
public void TearDown()
{
File.Delete(_tempFilePath);
}

[Test]
public void ReadLineText_WhenLineExists_ReturnsTrimmedText()
{
// Verifies the requested 1-based line is read and surrounding whitespace is trimmed.
string result = SourcePausePointSourceLineReader.ReadLineText(_tempFilePath, 2);

Assert.That(result, Is.EqualTo("line two"));
}

[Test]
public void ReadLineText_WhenLineNumberExceedsFileLength_ReturnsEmpty()
{
// Verifies a line number past the end of the file degrades to an empty string instead of throwing.
string result = SourcePausePointSourceLineReader.ReadLineText(_tempFilePath, 999);

Assert.That(result, Is.Empty);
}

[Test]
public void ReadLineText_WhenFileDoesNotExist_ReturnsEmpty()
{
// Verifies a missing file degrades to an empty string instead of throwing.
string result = SourcePausePointSourceLineReader.ReadLineText("/nonexistent/path/does-not-exist.cs", 1);

Assert.That(result, Is.Empty);
}

[Test]
public void ReadLineText_WhenLineNumberIsZeroOrNegative_ReturnsEmpty()
{
// Verifies non-positive line numbers (invalid 1-based input) degrade to an empty string.
Assert.That(SourcePausePointSourceLineReader.ReadLineText(_tempFilePath, 0), Is.Empty);
Assert.That(SourcePausePointSourceLineReader.ReadLineText(_tempFilePath, -1), Is.Empty);
}
}
}
11 changes: 11 additions & 0 deletions Assets/Tests/Editor/SourcePausePointSourceLineReaderTests.cs.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

13 changes: 13 additions & 0 deletions Packages/src/Editor/FirstPartyTools/PausePoint/PausePointTools.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
Expand Down Expand Up @@ -48,6 +49,7 @@ public class PausePointResponse : UnityCliLoopToolResponse
{
public string Id { get; set; } = string.Empty;
public int ResolvedLine { get; set; }
public string ResolvedLineText { get; set; } = string.Empty;
public string ResolvedMethod { get; set; } = string.Empty;
public string Status { get; set; } = string.Empty;
public bool IsEnabled { get; set; }
Expand Down Expand Up @@ -354,11 +356,22 @@ private static PausePointResponse EnableBySourceLocation(EnablePausePointSchema
parameters.MaxHistory);
PausePointResponse response = PausePointResponse.FromSnapshot(snapshot);
response.ResolvedLine = resolveResult.Resolution.ResolvedLine;
response.ResolvedLineText = ReadResolvedLineText(parameters.File, resolveResult.Resolution.ResolvedLine);
response.ResolvedMethod = resolveResult.Resolution.MethodDisplayName;
response.Warning = MergeWarnings(CreateEnableWarning(), patchResult.Warning);
return response;
}

// The resolved line can be rounded forward from the requested line (the Resolver picks
// the closest sequence point on or after it), so returning the actual source text lets
// the caller notice a mismatch immediately instead of assuming the requested line hit.
private static string ReadResolvedLineText(string requestedFile, int resolvedLine)
{
string normalizedFile = SourcePausePointPathNormalizer.ToForwardSlashes(requestedFile);
string absoluteFilePath = Path.Combine(UnityCliLoopPathResolver.GetProjectRoot(), normalizedFile);
return SourcePausePointSourceLineReader.ReadLineText(absoluteFilePath, resolvedLine);
}

// The derived id must use the originally requested file/line (not the resolved/rounded
// line) so repeated calls at the same requested location stay idempotent.
private static string BuildSourcePausePointId(string file, int line)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
using System.IO;
using System.Linq;

namespace io.github.hatayama.UnityCliLoop.FirstPartyTools
{
/// <summary>
/// Reads a single source line's trimmed text from disk, so a pause-point response can show
/// the AI agent exactly what code the resolved (possibly rounded-forward) line number maps to.
/// </summary>
internal static class SourcePausePointSourceLineReader
{
public static string ReadLineText(string absoluteFilePath, int lineNumber)
{
if (string.IsNullOrEmpty(absoluteFilePath) || lineNumber <= 0 || !File.Exists(absoluteFilePath))
{
return string.Empty;
}

string line = File.ReadLines(absoluteFilePath).Skip(lineNumber - 1).FirstOrDefault();
return line != null ? line.Trim() : string.Empty;
}
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.