Skip to content

Commit

Permalink
Add completion provider for filter parameters
Browse files Browse the repository at this point in the history
  • Loading branch information
graygilmore committed Oct 7, 2024
1 parent 03e8304 commit 32405cf
Show file tree
Hide file tree
Showing 7 changed files with 151 additions and 4 deletions.
12 changes: 12 additions & 0 deletions .changeset/smooth-toys-jump.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
'@shopify/theme-language-server-common': patch
---

Add completions support for filter parameters

The following liquid statement will now offer completion suggestions for filter
parameters:

```liquid
{{ product | image_url: width: 100, c█ }}
```
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
Provider,
RenderSnippetCompletionProvider,
TranslationCompletionProvider,
FilterNamedParameterCompletionProvider,
} from './providers';
import { GetSnippetNamesForURI } from './providers/RenderSnippetCompletionProvider';

Expand Down Expand Up @@ -57,6 +58,7 @@ export class CompletionsProvider {
new FilterCompletionProvider(typeSystem),
new TranslationCompletionProvider(documentManager, getTranslationsForURI),
new RenderSnippetCompletionProvider(getSnippetNamesForURI),
new FilterNamedParameterCompletionProvider(themeDocset),
];
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { describe, beforeEach, it, expect } from 'vitest';
import { DocumentManager } from '../../documents';
import { CompletionsProvider } from '../CompletionsProvider';

describe('Module: ObjectCompletionProvider', async () => {
let provider: CompletionsProvider;

beforeEach(async () => {
provider = new CompletionsProvider({
documentManager: new DocumentManager(),
themeDocset: {
filters: async () => [
{
parameters: [
{
description: '',
name: 'crop',
required: false,
types: ['string'],
},
{
description: '',
name: 'width',
required: false,
types: ['number'],
},
],
name: 'image_url',
},
],
objects: async () => [],
tags: async () => [],
systemTranslations: async () => ({}),
},
});
});

it('should complete filter parameter lookups', async () => {
const contexts = [
`{{ product | image_url: █`,
`{{ product | image_url: width: 100, █`,
`{{ product | image_url: 1, string, width: 100, █`,
`{{ product | image_url: width: 100 | image_url: █`,
];
await Promise.all(
contexts.map((context) => expect(provider, context).to.complete(context, ['crop', 'width'])),
);
});

describe('when the user has already begun typing a filter parameter', () => {
it('should filter options based on the text', async () => {
const contexts = [
`{{ product | image_url: c█`,
`{{ product | image_url: width: 100, c█`,
`{{ product | image_url: 1, string, width: 100, c█`,
`{{ product | image_url: width: 100 | image_url: c█`,
];
await Promise.all(
contexts.map((context) => expect(provider, context).to.complete(context, ['crop'])),
);
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { NodeTypes } from '@shopify/liquid-html-parser';
import { CompletionItem, CompletionItemKind } from 'vscode-languageserver';
import { CURSOR, LiquidCompletionParams } from '../params';
import { Provider, createCompletionItem } from './common';
import { ThemeDocset } from '@shopify/theme-check-common';

export class FilterNamedParameterCompletionProvider implements Provider {
constructor(private readonly themeDocset: ThemeDocset) {}

async completions(params: LiquidCompletionParams): Promise<CompletionItem[]> {
if (!params.completionContext) return [];

const { node } = params.completionContext;
if (!node || node.type !== NodeTypes.VariableLookup) {
return [];
}

if (!node.name || node.lookups.length > 0) {
// We only do top level in this one.
return [];
}

const partial = node.name.replace(CURSOR, '');
const currentContext = params.completionContext.ancestors.at(-1);

if (!currentContext || currentContext?.type !== NodeTypes.LiquidFilter) {
return [];
}

const filters = await this.themeDocset.filters();
const foundFilter = filters.find((f) => f.name === currentContext.name);

if (!foundFilter?.parameters) {
return [];
}

const options = foundFilter.parameters
.filter((p) => p.name.startsWith(partial))
.map((parameter) => {
return {
entry: {
...parameter,
},
types: parameter.types,
};
});

return options.map(({ entry, types }) =>
createCompletionItem(
entry,
{ kind: CompletionItemKind.TypeParameter },
'filter',
Array.isArray(types) ? types[0] : 'unknown',
),
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -160,4 +160,9 @@ describe('Module: ObjectCompletionProvider', async () => {
it('should not complete anything if there is nothing to complete', async () => {
await expect(provider).to.complete('{% assign x = "█" %}', []);
});

it('should not complete filter params', async () => {
const context = '{{ product | image_url: █';
await expect(provider, context).to.complete(context, []);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,13 @@ export class ObjectCompletionProvider implements Provider {
return [];
}

const currentContext = params.completionContext.ancestors.at(-1);
// We don't want to show generic variable completions when we're inside a
// filter. This case will be handled by FilterNamedParameterCompletionProvider.
if (currentContext?.type === NodeTypes.LiquidFilter) {
return [];
}

const partial = node.name.replace(CURSOR, '');
const options = await this.typeSystem.availableVariables(
partialAst,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
export { HtmlTagCompletionProvider } from './HtmlTagCompletionProvider';
export { Provider } from './common/Provider';
export { FilterCompletionProvider } from './FilterCompletionProvider';
export { FilterNamedParameterCompletionProvider } from './FilterNamedParameterCompletionProvider';
export { HtmlAttributeCompletionProvider } from './HtmlAttributeCompletionProvider';
export { HtmlAttributeValueCompletionProvider } from './HtmlAttributeValueCompletionProvider';
export { FilterCompletionProvider } from './FilterCompletionProvider';
export { HtmlTagCompletionProvider } from './HtmlTagCompletionProvider';
export { LiquidTagsCompletionProvider } from './LiquidTagsCompletionProvider';
export { ObjectAttributeCompletionProvider } from './ObjectAttributeCompletionProvider';
export { ObjectCompletionProvider } from './ObjectCompletionProvider';
export { TranslationCompletionProvider } from './TranslationCompletionProvider';
export { RenderSnippetCompletionProvider } from './RenderSnippetCompletionProvider';
export { Provider } from './common/Provider';
export { TranslationCompletionProvider } from './TranslationCompletionProvider';

0 comments on commit 32405cf

Please sign in to comment.