Public Access
256 lines
7.6 KiB
C#
256 lines
7.6 KiB
C#
using System.Text.RegularExpressions;
|
|
|
|
namespace MeetingAssistant;
|
|
|
|
internal static class AgentFileToolContent
|
|
{
|
|
public static string ReadLines(string content, int? from = null, int? to = null, int? tail = null)
|
|
{
|
|
if (!from.HasValue && !to.HasValue)
|
|
{
|
|
return tail.HasValue
|
|
? TailLines(content, Math.Clamp(tail.Value, 1, 2000))
|
|
: content;
|
|
}
|
|
|
|
var lines = SplitLines(content);
|
|
if (lines.Count == 0)
|
|
{
|
|
return "";
|
|
}
|
|
|
|
var start = Math.Clamp((from ?? 1) - 1, 0, lines.Count - 1);
|
|
var end = Math.Clamp((to ?? lines.Count) - 1, 0, lines.Count - 1);
|
|
if (end < start)
|
|
{
|
|
return "";
|
|
}
|
|
|
|
return string.Join('\n', lines.GetRange(start, end - start + 1));
|
|
}
|
|
|
|
public static string TailLines(string content, int count)
|
|
{
|
|
var lines = SplitLines(content);
|
|
if (lines.Count > 0 && lines[^1].Length == 0)
|
|
{
|
|
lines.RemoveAt(lines.Count - 1);
|
|
}
|
|
|
|
if (lines.Count <= count)
|
|
{
|
|
return string.Join('\n', lines);
|
|
}
|
|
|
|
return string.Join('\n', lines.GetRange(lines.Count - count, count));
|
|
}
|
|
|
|
public static string ApplyLineEdit(
|
|
string existingContent,
|
|
string replacementContent,
|
|
AgentFileEditMode editMode)
|
|
{
|
|
if (editMode.Kind == AgentFileEditKind.Overwrite)
|
|
{
|
|
return replacementContent;
|
|
}
|
|
|
|
if (editMode.Kind == AgentFileEditKind.Append)
|
|
{
|
|
return AppendContent(existingContent, replacementContent);
|
|
}
|
|
|
|
var lines = SplitLines(existingContent);
|
|
var replacementLines = SplitLines(replacementContent);
|
|
if (editMode.Kind == AgentFileEditKind.Insert)
|
|
{
|
|
var insertIndex = Math.Clamp(editMode.Insert!.Value - 1, 0, lines.Count);
|
|
lines.InsertRange(insertIndex, replacementLines);
|
|
return string.Join('\n', lines);
|
|
}
|
|
|
|
var startIndex = Math.Clamp(editMode.From!.Value - 1, 0, lines.Count);
|
|
var endIndexExclusive = Math.Clamp(editMode.To!.Value, 0, lines.Count);
|
|
if (endIndexExclusive < startIndex)
|
|
{
|
|
endIndexExclusive = startIndex;
|
|
}
|
|
|
|
lines.RemoveRange(startIndex, endIndexExclusive - startIndex);
|
|
lines.InsertRange(startIndex, replacementLines);
|
|
return string.Join('\n', lines);
|
|
}
|
|
|
|
public static async Task WriteFileContentAsync(
|
|
string filePath,
|
|
string content,
|
|
AgentFileEditMode editMode)
|
|
{
|
|
if (editMode.Kind == AgentFileEditKind.Overwrite)
|
|
{
|
|
await File.WriteAllTextAsync(filePath, content);
|
|
return;
|
|
}
|
|
|
|
if (editMode.Kind == AgentFileEditKind.Append)
|
|
{
|
|
var existing = File.Exists(filePath)
|
|
? await File.ReadAllTextAsync(filePath)
|
|
: "";
|
|
await File.WriteAllTextAsync(filePath, AppendContent(existing, content));
|
|
return;
|
|
}
|
|
|
|
var existingContent = File.Exists(filePath)
|
|
? await File.ReadAllTextAsync(filePath)
|
|
: "";
|
|
await File.WriteAllTextAsync(filePath, ApplyLineEdit(existingContent, content, editMode));
|
|
}
|
|
|
|
public static string AppendContent(string existingContent, string content)
|
|
{
|
|
if (string.IsNullOrEmpty(existingContent))
|
|
{
|
|
return content;
|
|
}
|
|
|
|
if (string.IsNullOrEmpty(content))
|
|
{
|
|
return existingContent;
|
|
}
|
|
|
|
var separator = existingContent.EndsWith('\n') || existingContent.EndsWith('\r')
|
|
? ""
|
|
: "\n";
|
|
return existingContent + separator + content;
|
|
}
|
|
|
|
public static List<string> SplitLines(string content)
|
|
{
|
|
if (content.Length == 0)
|
|
{
|
|
return [];
|
|
}
|
|
|
|
return content
|
|
.Replace("\r\n", "\n", StringComparison.Ordinal)
|
|
.Replace('\r', '\n')
|
|
.Split('\n')
|
|
.ToList();
|
|
}
|
|
|
|
public static bool IsWithinDirectory(string directory, string path)
|
|
{
|
|
var normalizedDirectory = Path.GetFullPath(directory)
|
|
.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar) + Path.DirectorySeparatorChar;
|
|
var normalizedPath = Path.GetFullPath(path);
|
|
return normalizedPath.StartsWith(normalizedDirectory, StringComparison.OrdinalIgnoreCase);
|
|
}
|
|
|
|
public static bool MatchesGlob(string path, string? pattern)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(pattern))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
var normalizedPath = ToToolPath(path);
|
|
var normalizedPattern = ToToolPath(pattern.Trim());
|
|
var placeholder = "\u0000";
|
|
var regexPattern = Regex.Escape(normalizedPattern)
|
|
.Replace("\\*\\*", placeholder, StringComparison.Ordinal)
|
|
.Replace("\\*", "[^/]*", StringComparison.Ordinal)
|
|
.Replace("\\?", "[^/]", StringComparison.Ordinal)
|
|
.Replace(placeholder, ".*", StringComparison.Ordinal);
|
|
return Regex.IsMatch(normalizedPath, $"^{regexPattern}$", RegexOptions.IgnoreCase);
|
|
}
|
|
|
|
public static string ToToolPath(string path)
|
|
{
|
|
return path.Replace(Path.DirectorySeparatorChar.ToString(), "/", StringComparison.Ordinal)
|
|
.Replace(Path.AltDirectorySeparatorChar.ToString(), "/", StringComparison.Ordinal);
|
|
}
|
|
|
|
public static string SearchFiles(
|
|
string keywords,
|
|
IEnumerable<AgentFileSearchSource> sources,
|
|
int maxMatches = 100,
|
|
RegexOptions regexOptions = RegexOptions.IgnoreCase)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(keywords))
|
|
{
|
|
return "";
|
|
}
|
|
|
|
try
|
|
{
|
|
var regex = new Regex(keywords, regexOptions);
|
|
var matches = new List<string>();
|
|
var limit = Math.Clamp(maxMatches, 1, 1000);
|
|
foreach (var source in sources)
|
|
{
|
|
var lines = File.ReadAllLines(source.Path);
|
|
for (var index = 0; index < lines.Length; index++)
|
|
{
|
|
if (regex.IsMatch(lines[index]))
|
|
{
|
|
matches.Add($"{ToToolPath(source.DisplayPath)}:{index + 1} {lines[index]}");
|
|
}
|
|
|
|
if (matches.Count >= limit)
|
|
{
|
|
return string.Join('\n', matches);
|
|
}
|
|
}
|
|
}
|
|
|
|
return string.Join('\n', matches);
|
|
}
|
|
catch
|
|
{
|
|
return "";
|
|
}
|
|
}
|
|
}
|
|
|
|
internal sealed record AgentFileSearchSource(string Path, string DisplayPath);
|
|
|
|
internal sealed record AgentFileEditMode(
|
|
AgentFileEditKind Kind,
|
|
int? From = null,
|
|
int? To = null,
|
|
int? Insert = null)
|
|
{
|
|
public static AgentFileEditMode? Create(int? from, int? to, int? insert, bool replaceFile)
|
|
{
|
|
if (replaceFile && (from.HasValue || to.HasValue || insert.HasValue))
|
|
{
|
|
return null;
|
|
}
|
|
|
|
if (insert.HasValue)
|
|
{
|
|
return from.HasValue || to.HasValue
|
|
? null
|
|
: new AgentFileEditMode(AgentFileEditKind.Insert, Insert: insert);
|
|
}
|
|
|
|
if (from.HasValue || to.HasValue)
|
|
{
|
|
return from.HasValue && to.HasValue && from.Value <= to.Value
|
|
? new AgentFileEditMode(AgentFileEditKind.Replace, From: from, To: to)
|
|
: null;
|
|
}
|
|
|
|
return new AgentFileEditMode(replaceFile ? AgentFileEditKind.Overwrite : AgentFileEditKind.Append);
|
|
}
|
|
}
|
|
|
|
internal enum AgentFileEditKind
|
|
{
|
|
Append,
|
|
Overwrite,
|
|
Replace,
|
|
Insert
|
|
}
|