基于Xtext LSP的项目配置对象自动关联及变更追踪方案咨询
针对你在Xtext LSP扩展中遇到的自动关联.projectConfig并追踪变更的问题,这里给出一套可落地的解决方案:
自动关联项目配置到Xtext资源并追踪变更
一、工作区激活时自动完成配置关联
1. 客户端监听工作区初始化事件
在VSCode侧的LSP扩展激活逻辑里,监听工作区就绪事件,扫描所有工作区文件夹下的.projectConfig文件,给服务端发送关联请求:
import { workspace, Uri } from 'vscode'; import { LanguageClient } from 'vscode-languageclient/node'; export function activate(context: vscode.ExtensionContext) { // ... 省略LanguageClient初始化代码 ... client.onReady().then(() => { // 初始化时扫描所有项目配置 scanProjectConfigs(client); // 监听工作区文件夹新增/移除事件 workspace.onDidChangeWorkspaceFolders(() => scanProjectConfigs(client)); }); } function scanProjectConfigs(client: LanguageClient) { workspace.workspaceFolders?.forEach(async folder => { const configUri = Uri.joinPath(folder.uri, '.projectConfig'); try { await workspace.openTextDocument(configUri); // 发送自定义请求触发服务端关联操作 await client.sendRequest('custom/attachProjectConfig', { configUri: configUri.toString(), folderUri: folder.uri.toString() }); } catch (e) { // 忽略无配置文件的项目 console.debug(`No .projectConfig found in ${folder.name}`); } }); }
2. 服务端处理关联请求
在Xtext服务端的自定义请求处理器中,解析.projectConfig并将ProjectConfigResourceDescription关联到目标文件夹下的所有Xtext资源:
import org.eclipse.xtext.ide.server.LanguageServerImpl; import org.eclipse.xtext.resource.IResourceDescription; import org.eclipse.xtext.resource.IResourceServiceProvider; @Inject private IResourceServiceProvider.Registry resourceServiceProviderRegistry; // 注册自定义请求能力 @Override protected void initializeCapabilities(InitializeParams params, InitializeResult result) { super.initializeCapabilities(params, result); result.getCapabilities().getExperimental().put("custom/attachProjectConfig", true); } @Override protected void registerHandlers(JsonRpcServer server) { super.registerHandlers(server); // 处理配置关联请求 server.registerMethod("custom/attachProjectConfig", params -> { String folderUri = params.get("folderUri").asText(); String configUri = params.get("configUri").asText(); // 解析配置文件为ProjectConfigResourceDescription实例 ProjectConfigResourceDescription config = parseProjectConfig(configUri); // 获取目标文件夹下的所有Xtext资源 Iterable<IResourceDescription> resources = getFolderResources(folderUri); for (IResourceDescription resourceDesc : resources) { // 避免重复关联 boolean hasExistingConfig = resourceDesc.getEObject().eAdapters().stream() .anyMatch(adapter -> adapter instanceof ProjectConfigResourceDescription); if (!hasExistingConfig) { resourceDesc.getEObject().eAdapters().add(config); } } return null; }); } // 实现.projectConfig文件解析逻辑 private ProjectConfigResourceDescription parseProjectConfig(String configUri) { ProjectConfigResourceDescription config = new ProjectConfigResourceDescription(); // 读取文件内容,填充groupId、artifactId、version等字段 return config; } // 实现获取指定文件夹下所有Xtext资源的逻辑 private Iterable<IResourceDescription> getFolderResources(String folderUri) { // 基于Xtext的ResourceDescriptionManager实现资源查询 // 示例逻辑需根据你的Xtext版本和项目结构调整 return Collections.emptyList(); }
二、追踪.projectConfig的变更并更新关联
1. 客户端监听文件变更
在VSCode客户端添加文件监听器,捕捉.projectConfig的创建、修改、删除事件,通知服务端更新:
// 在activate函数中添加 const configWatcher = workspace.createFileSystemWatcher('**/.projectConfig'); context.subscriptions.push( configWatcher.onDidChange(async uri => { const folder = workspace.getWorkspaceFolder(uri); if (folder) { await client.sendRequest('custom/updateProjectConfig', { configUri: uri.toString(), folderUri: folder.uri.toString() }); } }), configWatcher.onDidCreate(async uri => { const folder = workspace.getWorkspaceFolder(uri); if (folder) { await client.sendRequest('custom/attachProjectConfig', { configUri: uri.toString(), folderUri: folder.uri.toString() }); } }), configWatcher.onDidDelete(async uri => { const folder = workspace.getWorkspaceFolder(uri); if (folder) { await client.sendRequest('custom/removeProjectConfig', { folderUri: folder.uri.toString() }); } }) );
2. 服务端更新关联配置
在服务端实现更新/移除配置的请求处理,替换资源上的旧Adapter并通知资源变更:
// 注册更新配置请求 server.registerMethod("custom/updateProjectConfig", params -> { String folderUri = params.get("folderUri").asText(); String configUri = params.get("configUri").asText(); ProjectConfigResourceDescription newConfig = parseProjectConfig(configUri); Iterable<IResourceDescription> resources = getFolderResources(folderUri); for (IResourceDescription resourceDesc : resources) { // 移除旧配置,添加新配置 resourceDesc.getEObject().eAdapters().removeIf(adapter -> adapter instanceof ProjectConfigResourceDescription); resourceDesc.getEObject().eAdapters().add(newConfig); // 触发资源变更事件,通知代码生成器等模块更新 resourceServiceProviderRegistry.getResourceServiceProvider(resourceDesc.getURI()) .getResourceDescriptionManager() .notifyChanged(new ResourceDescriptionChangeEvent(resourceDesc)); } return null; }); // 注册移除配置请求 server.registerMethod("custom/removeProjectConfig", params -> { String folderUri = params.get("folderUri").asText(); Iterable<IResourceDescription> resources = getFolderResources(folderUri); for (IResourceDescription resourceDesc : resources) { resourceDesc.getEObject().eAdapters().removeIf(adapter -> adapter instanceof ProjectConfigResourceDescription); } return null; });
三、在业务模块中访问配置
在代码生成器、CommandContribution等模块中,通过EMF的Adapter机制直接获取配置:
// 代码生成器示例 public class CustomGenerator extends AbstractGenerator { @Override protected void doGenerate(Resource resource, IFileSystemAccess2 fsa, IGeneratorContext context) { ProjectConfigResourceDescription projectConfig = resource.eAdapters().stream() .filter(adapter -> adapter instanceof ProjectConfigResourceDescription) .map(ProjectConfigResourceDescription.class::cast) .findFirst() .orElseThrow(() -> new IllegalStateException("Project config not attached")); // 使用配置信息:projectConfig.getGroupId()、projectConfig.getArtifactId()等 generateCodeWithConfig(projectConfig, fsa); } }
内容的提问来源于stack exchange,提问作者We_wake
相关产品推荐
相关产品推荐

