You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.12 10:22:28