using System.Diagnostics; namespace CodePlay.Core.Services; /// /// 代码格式化服务 /// 支持 C#/Java/C++ 代码格式化 /// public interface ICodeFormatter { Task FormatAsync(string code, string language, CancellationToken cancellationToken = default); bool IsFormatterAvailable(string language); } public class CodeFormatter : ICodeFormatter { public async Task FormatAsync(string code, string language, CancellationToken cancellationToken = default) { return language.ToLower() switch { "csharp" => await FormatCSharp(code, cancellationToken), "java" => await FormatJava(code, cancellationToken), "cpp" or "c++" => await FormatCpp(code, cancellationToken), _ => code }; } public bool IsFormatterAvailable(string language) { return language.ToLower() switch { "csharp" => IsDotNetFormatAvailable(), "java" => IsJavaFormatAvailable(), "cpp" or "c++" => IsClangFormatAvailable(), _ => false }; } private async Task FormatCSharp(string code, CancellationToken ct) { if (!IsDotNetFormatAvailable()) return code; try { var tempFile = Path.GetTempFileName() + ".cs"; await File.WriteAllTextAsync(tempFile, code, ct); var startInfo = new ProcessStartInfo { FileName = "dotnet", Arguments = $"format \"{tempFile}\" --no-restore", RedirectStandardOutput = true, RedirectStandardError = true, UseShellExecute = false, CreateNoWindow = true }; using var process = Process.Start(startInfo); if (process == null) return code; await process.WaitForExitAsync(ct); if (process.ExitCode == 0) return await File.ReadAllTextAsync(tempFile, ct); return code; } catch { return code; } } private async Task FormatJava(string code, CancellationToken ct) { if (!IsJavaFormatAvailable()) return code; try { var tempFile = Path.GetTempFileName() + ".java"; await File.WriteAllTextAsync(tempFile, code, ct); var startInfo = new ProcessStartInfo { FileName = "google-java-format", Arguments = $"--replace \"{tempFile}\"", RedirectStandardOutput = true, RedirectStandardError = true, UseShellExecute = false, CreateNoWindow = true }; using var process = Process.Start(startInfo); if (process == null) return code; await process.WaitForExitAsync(ct); if (process.ExitCode == 0) return await File.ReadAllTextAsync(tempFile, ct); return code; } catch { return code; } } private async Task FormatCpp(string code, CancellationToken ct) { if (!IsClangFormatAvailable()) return code; try { var tempFile = Path.GetTempFileName() + ".cpp"; await File.WriteAllTextAsync(tempFile, code, ct); var startInfo = new ProcessStartInfo { FileName = "clang-format", Arguments = $"-i -style=file \"{tempFile}\"", RedirectStandardOutput = true, RedirectStandardError = true, UseShellExecute = false, CreateNoWindow = true }; using var process = Process.Start(startInfo); if (process == null) return code; await process.WaitForExitAsync(ct); if (process.ExitCode == 0) return await File.ReadAllTextAsync(tempFile, ct); return code; } catch { return code; } } private bool IsDotNetFormatAvailable() { try { var startInfo = new ProcessStartInfo { FileName = "dotnet", Arguments = "tool list -g", RedirectStandardOutput = true, UseShellExecute = false, CreateNoWindow = true }; using var process = Process.Start(startInfo); process?.WaitForExit(5000); var output = process?.StandardOutput.ReadToEnd(); return output?.Contains("dotnet-format") == true; } catch { return false; } } private bool IsJavaFormatAvailable() { try { var startInfo = new ProcessStartInfo { FileName = "google-java-format", Arguments = "--version", RedirectStandardOutput = true, UseShellExecute = false, CreateNoWindow = true }; using var process = Process.Start(startInfo); process?.WaitForExit(5000); return process?.ExitCode == 0; } catch { return false; } } private bool IsClangFormatAvailable() { try { var startInfo = new ProcessStartInfo { FileName = "clang-format", Arguments = "--version", RedirectStandardOutput = true, UseShellExecute = false, CreateNoWindow = true }; using var process = Process.Start(startInfo); process?.WaitForExit(5000); return process?.ExitCode == 0; } catch { return false; } } }