Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support "go to definition" for virtual files provided through language modules #2561

Closed
rchl opened this issue Mar 30, 2023 · 7 comments
Closed
Labels
question Further information is requested

Comments

@rchl
Copy link
Collaborator

rchl commented Mar 30, 2023

See #889 (comment)

How would you imagine this working? Would it have to be a custom request that provides content of the virtual file?
Ideally it would go straight to the relevant Vue file but not sure if that would be doable.

@johnsoncodehk johnsoncodehk added the enhancement New feature or request label Mar 30, 2023
@johnsoncodehk
Copy link
Member

johnsoncodehk commented Apr 16, 2023

This work for me.

const ts = require('typescript');
const path = require('path');
const volar = require('@volar/language-core');
const snapshotToMirrorMappings = new WeakMap();

/** @type {import('@volar/language-core').LanguageModule} */
module.exports = {
    createFile(fileName, snapshot) {
        if (fileName.endsWith('/generated-component-types.d.ts')) {
            return {
                fileName,
                snapshot,
                capabilities: {},
                embeddedFiles: [],
                kind: volar.FileKind.TypeScriptHostFile,
                mappings: [{
                    data: {},
                    sourceRange: [0, snapshot.getLength()],
                    generatedRange: [0, snapshot.getLength()],
                }],
                mirrorBehaviorMappings: snapshotToMirrorMappings.get(snapshot),
            };
        }
    },
    updateFile(file, newSnapshot) {
        file.snapshot = newSnapshot;
        file.mappings = [{
            data: {},
            sourceRange: [0, newSnapshot.getLength()],
            generatedRange: [0, newSnapshot.getLength()],
        }];
        file.mirrorBehaviorMappings = snapshotToMirrorMappings.get(newSnapshot);
    },
    proxyLanguageServiceHost(host) {
        const vueTypesScript = {
            projectVersion: '',
            fileName: host.getCurrentDirectory() + '/generated-component-types.d.ts',
            _version: 0,
            _snapshot: ts.ScriptSnapshot.fromString(''),
            get version() {
                this.update();
                return this._version;
            },
            get snapshot() {
                this.update();
                return this._snapshot;
            },
            update() {
                if (!this._snapshot) {
                    return
                }
                if (!host.getProjectVersion || host.getProjectVersion() !== this.projectVersion) {
                    this.projectVersion = host.getProjectVersion?.() ?? '';
                    const [newText, mirrorMappings] = this.generateText();
                    if (newText !== this._snapshot.getText(0, this._snapshot.getLength())) {
                        this._version++;
                        this._snapshot = ts.ScriptSnapshot.fromString(newText);
                        snapshotToMirrorMappings.set(this._snapshot, mirrorMappings);
                    }
                }
            },
            generateText() {
                const mirrorMappings = [];
                let code = '';
                code += `declare module 'vue' {\n`;
                code += `export interface GlobalComponents {\n`;
                for (const fileName of host.getScriptFileNames()) {
                    if (fileName.endsWith('.vue')) {
                        const dirName = path.dirname(fileName);
                        const baseName = path.basename(fileName);
                        const componentName = baseName.replace('.vue', '');
                        const left = [code.length, code.length + componentName.length];
                        code += `${componentName}: typeof import('./${path.relative(host.getCurrentDirectory(), dirName)}/`;
                        const right = [code.length, code.length + baseName.length];
                        code += `${baseName}').default;\n`;
                        mirrorMappings.push({
                            data: [volar.MirrorBehaviorCapabilities.full, volar.MirrorBehaviorCapabilities.full],
                            sourceRange: left,
                            generatedRange: right,
                        });
                    }
                }
                code += `}\n`;
                code += `}\n`;
                code += `export { };\n`;
                return [code, mirrorMappings];
            },
        };

        return {
            getScriptFileNames() {
                return [
                    ...host.getScriptFileNames(),
                    vueTypesScript.fileName,
                ];
            },
            getScriptVersion(fileName) {
                if (fileName === vueTypesScript.fileName) {
                    return String(vueTypesScript.version);
                }
                return host.getScriptVersion(fileName);
            },
            getScriptSnapshot(fileName) {
                if (fileName === vueTypesScript.fileName) {
                    return vueTypesScript.snapshot;
                }
                return host.getScriptSnapshot(fileName);
            },
        }
    },
};

@johnsoncodehk johnsoncodehk added question Further information is requested and removed enhancement New feature or request labels Apr 16, 2023
@rchl
Copy link
Collaborator Author

rchl commented Apr 16, 2023

Tried to test it but things are broken now.

const plugin: LanguageServerPlugin = (initOptions: VueServerInitializationOptions): ReturnType<LanguageServerPlugin> => {
needs to be updated to match the volarjs/volar.js@5a5947c change

@rchl
Copy link
Collaborator Author

rchl commented Apr 16, 2023

Reverted @volar/language-server to v1.4.0-alpha.9 to make that work but I can't confirm that "go to definition" works. For me the request returns empty result.

@johnsoncodehk
Copy link
Member

You can try it by @volar/vue-language-server@1.3.16 + @volar/language-core@next with johnsoncodehk/volar-starter@2ed7a7f.

@rchl
Copy link
Collaborator Author

rchl commented Apr 17, 2023

I've confirmed that that works but it doesn't work with defineComponent + Vue 2.7.

Modified volar-starter at https://github.com/rchl/volar-starter/tree/auto-global-components-define-component (the auto-global-components-define-component branch).

Here are the changes that I've made to your branch: rchl/volar-starter@db0d847

@johnsoncodehk johnsoncodehk reopened this Apr 17, 2023
@johnsoncodehk
Copy link
Member

johnsoncodehk commented Apr 23, 2023

It seems to have worked since v1.4.0. (Depend on volarjs/volar.js@d51a54d)

@rchl
Copy link
Collaborator Author

rchl commented Apr 23, 2023

Thanks.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
question Further information is requested
Projects
None yet
Development

No branches or pull requests

2 participants