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

Improve the JSON completion & hover building blocks #622

Merged
merged 1 commit into from
Nov 29, 2024

Conversation

charlespwd
Copy link
Contributor

@charlespwd charlespwd commented Nov 28, 2024

What are you adding in this PR?

The json-languageservice API was a bit awkward to use. Had a bunch of weird type problems that nobody should really have to care about.

In this PR, I use an adapter to make folks write Completion & Hover providers in a manner similar to what we do with liquid files.

It's not 100% the same, but it's similar enough and abstracts the weird quirks like "return undefined not Promise when you can't hover something".

What's next? Any followup issues?

Provide examples of how to do more complex completions with dependencies:

Before you deploy

  • I included a bump changeset

@charlespwd charlespwd requested a review from aswamy November 28, 2024 19:07
@charlespwd charlespwd force-pushed the feature/block-type-completion-example branch 6 times, most recently from 1d2ce53 to 4377ae2 Compare November 28, 2024 19:39
The json-languageservice API was a bit awkward to use. Had a bunch of
weird type problems that nobody should really have to care about.

In this PR, I use an adapter to make folks write Completion & Hover
providers in a manner similar to what we do with liquid files.

It's not 100% the same, but it's similar enough and abstracts the weird
quirks like "return undefined not Promise<undefined> when you can't
hover something".
@charlespwd charlespwd force-pushed the feature/block-type-completion-example branch from 4377ae2 to efe4357 Compare November 28, 2024 19:41
Comment on lines +17 to +52
async completeValue(context: RequestContext, path: JSONPath): Promise<JSONCompletionItem[]> {
if (!fileMatch(context.doc.uri, this.uriPatterns) || !isLiquidRequestContext(context)) {
return [];
}

const { doc, parsed } = context;

const label = deepGet(parsed, path);
if (!label || typeof label !== 'string' || !label.startsWith('t:')) {
return [];
}

const partial = /^t:(.*)/.exec(label)?.[1];
if (partial === undefined) return [];

const translations = await this.getDefaultSchemaTranslations(doc.uri);

// We'll let the frontend do the filtering. But we'll only include shopify
// translations if the shopify prefix is present
const options = translationOptions(translations);

return options.map((option): JSONCompletionItem => {
const tLabel = `t:${option.path.join('.')}`;
return {
label: tLabel,
kind: CompletionItemKind.Value,
filterText: `"${tLabel}"`,
insertText: `"${tLabel}"`,
insertTextFormat: 1,
documentation: {
kind: 'markdown',
value: renderTranslation(option.translation),
},
};
});
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is how "easy" I'd like it would be to write a custom JSON completion inside a {% schema %}

Comment on lines -70 to -103
async collectValueCompletions(
uri: string,
location: JSONPath,
propertyKey: string,
result: CompletionsCollector,
) {
if (!uriMatch(uri, this.uriPatterns)) return;
const doc = this.documentManager.get(uri);
if (!doc || doc.ast instanceof Error || doc.type !== SourceCodeType.LiquidHtml) {
return;
}

const schema = findSchemaNode(doc.ast);
if (!schema) return;

const jsonString = schema.source.slice(
schema.blockStartPosition.end,
schema.blockEndPosition.start,
);
const jsonDocument = parseJSON(jsonString);
if (!jsonDocument) return;

const label = location
.concat(propertyKey)
.reduce((acc: any, val: any) => acc?.[val], jsonDocument);
if (!label || typeof label !== 'string' || !label.startsWith('t:')) {
return;
}

const items = await this.recommendTranslations(uri, label);
for (const item of items) {
result.add(item);
}
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This thing was split into the SchemaTranslationCompletionProvider

Comment on lines -36 to -68
getInfoContribution(uri: string, location: JSONPath): Promise<string[]> {
if (!uriMatch(uri, this.uriPatterns)) return undefined as any;
const doc = this.documentManager.get(uri);
if (
!doc ||
location.length === 0 ||
doc.ast instanceof Error ||
doc.type !== SourceCodeType.LiquidHtml
) {
return undefined as any;
}

const schema = findSchemaNode(doc.ast);
if (!schema) return undefined as any;

const jsonString = schema.source.slice(
schema.blockStartPosition.end,
schema.blockEndPosition.start,
);
const jsonDocument = parseJSON(jsonString);
if (isError(jsonDocument)) return undefined as any;

const label = location.reduce((acc: any, val: any) => acc?.[val], jsonDocument);
if (!label || typeof label !== 'string' || !label.startsWith('t:')) return undefined as any;

return this.getDefaultSchemaTranslations(uri).then((translations) => {
const path = label.slice(2);
const value = translationValue(path, translations);
if (!value) return undefined as any;

return [renderTranslation(value)];
});
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This thing was split into the SchemaTranslationHoverProvider

this.documentManager,
this.getDefaultSchemaTranslations,
),
new JSONContributions(this.documentManager, this.getDefaultSchemaTranslations),
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devs shouldn't need to worry about this anymore other than ferrying new dependencies in there.

Copy link
Contributor

@aswamy aswamy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The rework looks so clean 🧹

Some comments are really for things outside this ticket. Functionally everything works as expected.

.changeset/nice-candles-sparkle.md Show resolved Hide resolved
insertTextFormat: 1,
documentation: {
kind: 'markdown',
value: renderTranslation(option.translation),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we render it as markdown in vscode? The links don't work:
image

The link points to /admin/settings/markets. if you click on it in VSCode, it attempts to create a file.

Copy link
Contributor Author

@charlespwd charlespwd Nov 29, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like your attention to detail.. but to what store would we make the links to? The extension doesn't have that kind of context right now. Only the CLI does.

I think it's fine to ignore IMO

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But yeah maybe we render those as text (not markdown)... idk. I don't feel strongly about either way.

Comment on lines +39 to +41
const tLabel = `t:${option.path.join('.')}`;
return {
label: tLabel,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can probably have this as a low-priority ticket, but we need a better way for the text to overflow when doing translation completion suggestions:

image

I'd rather see "...blocks.annoucement-bar.name". There really isn't a need to see what we already typed beyond the last previous ..

i.e. to determine the label of the auto-completion, we could do something like:

  • split the translation path by "."
  • if you already typed "t:small.piece.of.text" put an "..." between all the parts we already typed and "t:" up to the latest leg of the path: "t:...text"

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thing is that search is fuzzy... I feel like that's a client side concern? 🤔

Comment on lines +47 to +50
if (!doc) return SKIP_CONTRIBUTION;
const context = this.getContext(doc);
const provider = this.hoverProviders.find((p) => p.canHover(context, location));
if (!provider) return SKIP_CONTRIBUTION;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried to use Promise.reject() instead of SKIP_CONTRIBUTION and it threw an error, but why can't we have Promise.resolve([])?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because then the contributions that you'd get from the default JSON schema would no longer show up. It's awkward af.

https://github.com/microsoft/vscode-json-languageservice/blob/main/src/services/jsonHover.ts#L56-L59

I use SKIP_CONTRIBUTION because the result is only ignored if you return falsy. And Promise is truthy.

Comment on lines +30 to +31
const path = label.slice(2); // remove `t:`
const value = translationValue(path, translations);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should translationValue just strip the t: from the path if it exists? I feel like we'd be doing this for any new translation providers we make.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like path is clearer than tPathOrPath? I get that it could be nicer to use, but idk how often we'd use this?

@charlespwd charlespwd merged commit 05b928e into main Nov 29, 2024
6 checks passed
@charlespwd charlespwd deleted the feature/block-type-completion-example branch November 29, 2024 17:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants