@shopify/theme-language-server-common
Version:
<h1 align="center" style="position: relative;" > <br> <img src="https://github.com/Shopify/theme-check-vscode/blob/main/images/shopify_glyph.png?raw=true" alt="logo" width="141" height="160"> <br> Theme Language Server </h1>
150 lines (134 loc) • 5.7 kB
text/typescript
import {
AppBlockSchema,
parseJSON,
SectionSchema,
SourceCodeType,
ThemeBlockSchema,
} from '@shopify/theme-check-common';
import {
CompletionsCollector,
JSONPath,
JSONWorkerContribution,
MarkedString,
} from 'vscode-json-languageservice';
import { AugmentedSourceCode, DocumentManager } from '../documents';
import { GetTranslationsForURI } from '../translations';
import { JSONCompletionProvider } from './completions/JSONCompletionProvider';
import { BlockTypeCompletionProvider } from './completions/providers/BlockTypeCompletionProvider';
import { ReferencedBlockTypeCompletionProvider } from './completions/providers/ReferencedBlockTypeCompletionProvider';
import { SchemaTranslationsCompletionProvider } from './completions/providers/SchemaTranslationCompletionProvider';
import { JSONHoverProvider } from './hover/JSONHoverProvider';
import { SchemaTranslationHoverProvider } from './hover/providers/SchemaTranslationHoverProvider';
import { TranslationPathHoverProvider } from './hover/providers/TranslationPathHoverProvider';
import { RequestContext } from './RequestContext';
import { findSchemaNode } from './utils';
import { SettingsPropertyCompletionProvider } from './completions/providers/SettingsPropertyCompletionProvider';
import { SettingsHoverProvider } from './hover/providers/SettingsHoverProvider';
import { BlockSettingsPropertyCompletionProvider } from './completions/providers/BlockSettingsPropertyCompletionProvider';
import { BlockSettingsHoverProvider } from './hover/providers/BlockSettingsHoverProvider';
/** The getInfoContribution API will only fallback if we return undefined synchronously */
const SKIP_CONTRIBUTION = undefined as any;
export type GetThemeBlockSchema = (
uri: string,
name: string,
) => Promise<SectionSchema | ThemeBlockSchema | AppBlockSchema | undefined>;
export type GetThemeBlockNames = (uri: string, includePrivate: boolean) => Promise<string[]>;
/**
* I'm not a fan of how json-languageservice does its feature contributions. It's too different
* from everything else we do in here.
*
* Instead, we'll have this little adapter that makes the completions and hover providers feel
* a bit more familiar.
*/
export class JSONContributions implements JSONWorkerContribution {
private hoverProviders: JSONHoverProvider[];
private completionProviders: JSONCompletionProvider[];
constructor(
private documentManager: DocumentManager,
getDefaultSchemaTranslations: GetTranslationsForURI,
getThemeBlockNames: GetThemeBlockNames,
getThemeBlockSchema: GetThemeBlockSchema,
) {
this.hoverProviders = [
new TranslationPathHoverProvider(),
new SchemaTranslationHoverProvider(getDefaultSchemaTranslations),
new SettingsHoverProvider(getDefaultSchemaTranslations),
new BlockSettingsHoverProvider(getDefaultSchemaTranslations, getThemeBlockSchema),
];
this.completionProviders = [
new SchemaTranslationsCompletionProvider(getDefaultSchemaTranslations),
new BlockTypeCompletionProvider(getThemeBlockNames),
new ReferencedBlockTypeCompletionProvider(getThemeBlockNames, getThemeBlockSchema),
new BlockSettingsPropertyCompletionProvider(
getDefaultSchemaTranslations,
getThemeBlockSchema,
),
new SettingsPropertyCompletionProvider(getDefaultSchemaTranslations),
];
}
getInfoContribution(uri: string, location: JSONPath): Promise<MarkedString[]> {
const doc = this.documentManager.get(uri);
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;
return provider.hover(context, location);
}
async collectPropertyCompletions(
uri: string,
location: JSONPath,
// Don't know what those three are for.
_currentWord: string,
_addValue: boolean,
_isLast: boolean,
result: CompletionsCollector,
): Promise<void> {
const doc = this.documentManager.get(uri);
if (!doc || doc.ast instanceof Error) return;
const items = await Promise.all(
this.completionProviders
.filter((provider) => provider.completeProperty)
.map((provider) => provider.completeProperty!(this.getContext(doc), location)),
);
for (const item of items.flat()) {
result.add(item);
}
}
async collectValueCompletions(
uri: string,
location: JSONPath,
propertyKey: string,
result: CompletionsCollector,
): Promise<void> {
const doc = this.documentManager.get(uri);
if (!doc || doc.ast instanceof Error) return;
const items = await Promise.all(
this.completionProviders
.filter((provider) => provider.completeValue)
.map((provider) =>
provider.completeValue!(this.getContext(doc), location.concat(propertyKey)),
),
);
for (const item of items.flat()) {
result.add(item);
}
}
/** I'm not sure we want to do anything with that... but TS requires us to have it */
async collectDefaultCompletions(_uri: string, _result: CompletionsCollector): Promise<void> {}
private getContext(doc: AugmentedSourceCode): RequestContext {
const context: RequestContext = {
doc,
};
if (doc.type === SourceCodeType.LiquidHtml && !(doc.ast instanceof Error)) {
const schema = findSchemaNode(doc.ast);
if (!schema) return SKIP_CONTRIBUTION;
const jsonString = schema?.source.slice(
schema.blockStartPosition.end,
schema.blockEndPosition.start,
);
context.schema = schema;
context.parsed = parseJSON(jsonString);
}
return context;
}
}