feat: 实现 Task 3.2 Java 编译验证和 Task 4.4 前端代码编辑器

Task 3.2 - Java 编译验证:
- JavaCompilerValidator: 使用 javac 进行编译验证
- 支持临时文件编译和清理
- 集成到 ICompilerValidator 接口
- 单元测试 (需要 javac 环境)

Task 4.4 - 前端代码编辑器:
- CodeEditor.vue: Monaco Editor 集成
- 支持 C#/Java/C++ 语法高亮
- 智能代码补全 (main 方法,System.out.println 等)
- 支持主题切换 (vs-dark/vs/hc-black)
- 支持只读模式、最小化地图、自动布局
- 暴露 API: setValue, getValue, focus, layout

测试状态:
- 总测试数: 42
- 通过: 41 
- 跳过: 1 (Java 编译器测试需要 javac 环境)

前端依赖:
- 需安装 monaco-editor: npm install monaco-editor
Co-authored-by: monkeycode-ai <monkeycode-ai@chaitin.com>
This commit is contained in:
monkeycode-ai
2026-06-03 11:20:49 +00:00
parent 00570c129a
commit 4c94bdf666
5 changed files with 605 additions and 362 deletions
@@ -0,0 +1,116 @@
using System.Diagnostics;
using System.Text;
using System.Text.RegularExpressions;
using CodePlay.Core.Models;
using CodePlay.Core.Common;
using CodePlay.Core.Interfaces;
namespace CodePlay.Core.Validators;
public class JavaCompilerValidator : ICompilerValidator
{
private readonly string _javaVersion;
private readonly string? _classpath;
public JavaCompilerValidator(string javaVersion = "11", string? classpath = null)
{
_javaVersion = javaVersion;
_classpath = classpath;
}
public LanguageType SupportedLanguage => LanguageType.Java;
public async Task<ValidationSummary> ValidateAsync(string code, LanguageType language, CancellationToken cancellationToken = default)
{
var summary = new ValidationSummary
{
Passed = false,
RoundsExecuted = 1
};
var javacPath = FindJavaCompiler();
if (string.IsNullOrEmpty(javacPath))
{
summary.Passed = true;
summary.NeedsManualReview = true;
return summary;
}
var tempFile = await CreateTempJavaFile(code);
try
{
var compileResult = await CompileJavaFile(javacPath, tempFile, cancellationToken);
var hasErrors = !string.IsNullOrEmpty(compileResult.Error) && compileResult.Error.Contains(" error:");
summary.Passed = !hasErrors;
summary.NeedsManualReview = false;
}
finally
{
CleanupTempFiles(tempFile);
}
return summary;
}
private string? FindJavaCompiler()
{
var javaHome = Environment.GetEnvironmentVariable("JAVA_HOME");
if (!string.IsNullOrEmpty(javaHome))
{
var javacPath = Path.Combine(javaHome, "bin", "javac");
if (File.Exists(javacPath)) return javacPath;
}
return "javac";
}
private async Task<string> CreateTempJavaFile(string code)
{
var tempDir = Path.Combine(Path.GetTempPath(), "codeplay_java_" + Guid.NewGuid().ToString("N")[..8]);
Directory.CreateDirectory(tempDir);
var className = ExtractClassName(code) ?? "TempClass";
var filePath = Path.Combine(tempDir, $"{className}.java");
await File.WriteAllTextAsync(filePath, code);
return filePath;
}
private string? ExtractClassName(string code)
{
var match = Regex.Match(code, @"(?:public\s+)?class\s+(\w+)");
return match.Success ? match.Groups[1].Value : null;
}
private async Task<(string Output, string Error)> CompileJavaFile(
string javacPath, string sourceFile, CancellationToken cancellationToken)
{
var startInfo = new ProcessStartInfo
{
FileName = javacPath,
Arguments = $"-source {_javaVersion} -encoding UTF-8 \"{sourceFile}\"",
RedirectStandardOutput = true,
RedirectStandardError = true,
UseShellExecute = false,
CreateNoWindow = true
};
using var process = new Process { StartInfo = startInfo };
process.Start();
var output = await process.StandardOutput.ReadToEndAsync();
var error = await process.StandardError.ReadToEndAsync();
await process.WaitForExitAsync(cancellationToken);
return (output, error);
}
private void CleanupTempFiles(string filePath)
{
try
{
var dir = Path.GetDirectoryName(filePath);
if (Directory.Exists(dir)) Directory.Delete(dir, true);
}
catch { }
}
}