Skip to content

Commit

Permalink
Gate the "preload" functionality behind themeCheck.preloadOnBoot in…
Browse files Browse the repository at this point in the history
…itialization option (#589)

We probably don't want to preload all the files just yet.

We can now start the `CodeMirrorLanguageClient` with the following to disable full theme preload on theme file open.

```
const client = new CodeMirrorLanguageClient(worker, {
  initializationOptions: {
    'themeCheck.preloadOnBoot': false,
  }
});
```
  • Loading branch information
charlespwd authored Nov 13, 2024
1 parent f09c923 commit 0b7534b
Show file tree
Hide file tree
Showing 12 changed files with 97 additions and 33 deletions.
9 changes: 9 additions & 0 deletions .changeset/fast-ears-notice.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
'@shopify/theme-language-server-common': patch
---

Add a way to disable the theme preload on boot

The `themeCheck.preloadOnBoot` (default: `true`) configuration / initializationOption can be set to `false` to prevent the preload of all theme files as soon as a theme file is opened.

We'll use this to disable preload in admin.
5 changes: 5 additions & 0 deletions .changeset/swift-jeans-smash.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@shopify/codemirror-language-client': patch
---

Add way to pass LSP initialization options to the server with the initialize request
19 changes: 17 additions & 2 deletions packages/codemirror-language-client/playground/src/playground.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,11 @@ async function main() {

const client = new CodeMirrorLanguageClient(
worker,
{},
{
initializationOptions: {
'themeCheck.preloadOnBoot': false,
},
},
{
autocompleteOptions: {
activateOnTyping: true,
Expand Down Expand Up @@ -125,16 +129,24 @@ async function main() {
return JSON.stringify(exampleTranslations, null, 2);
case 'browser:/locales/en.default.schema.json':
return JSON.stringify(exampleSchemaTranslations, null, 2);
case 'browser:/snippets/article-card.liquid':
case 'browser:/snippets/product-card.liquid':
case 'browser:/snippets/product.liquid':
return '';
default:
throw new Error(`File does not exist ${uri}`);
}
});

client.client.onRequest('fs/stat' as any, ([uri]: string) => {
switch (uri) {
case 'browser:/sections/section.liquid':
case 'browser:/.theme-check.yml':
case 'browser:/locales/en.default.json':
case 'browser:/locales/en.schema.default.json':
case 'browser:/sections/section.liquid':
case 'browser:/snippets/article-card.liquid':
case 'browser:/snippets/product-card.liquid':
case 'browser:/snippets/product.liquid':
return { fileType: 1, size: 1 };
default:
throw new Error(`File does not exist: ${uri}`);
Expand All @@ -151,6 +163,9 @@ async function main() {
['browser:/.theme-check.yml', 1],
];
}
case 'browser:/sections': {
return [['browser:/sections/section.liquid', 1]];
}
case 'browser:/snippets': {
return [
['browser:/snippets/article-card.liquid', 1],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ export class CodeMirrorLanguageClient {

constructor(
private readonly worker: Worker,
{ log = defaultLogger }: ClientDependencies = {},
{ log = defaultLogger, initializationOptions }: ClientDependencies = {},
{
infoRenderer,
autocompleteOptions,
Expand All @@ -126,6 +126,7 @@ export class CodeMirrorLanguageClient {
) {
this.client = new LanguageClient(worker, {
clientCapabilities,
initializationOptions,
log,
});
this.worker = worker;
Expand Down
4 changes: 4 additions & 0 deletions packages/codemirror-language-client/src/LanguageClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ export interface PromiseCompletion {

export interface Dependencies {
clientCapabilities: ClientCapabilities;
initializationOptions?: any;
log(...args: any[]): void;
}

Expand All @@ -48,6 +49,7 @@ export interface AbstractLanguageClient {

export class LanguageClient extends EventTarget implements AbstractLanguageClient {
public readonly clientCapabilities: ClientCapabilities;
public readonly initializationOptions: any;
public serverCapabilities: ServerCapabilities | null;
public serverInfo: any;

Expand All @@ -64,6 +66,7 @@ export class LanguageClient extends EventTarget implements AbstractLanguageClien
this.dispose = () => {};
this.disposables = [];
this.clientCapabilities = dependencies.clientCapabilities;
this.initializationOptions = dependencies.initializationOptions;
this.log = dependencies.log;
this.serverCapabilities = null;
this.serverInfo = null;
Expand Down Expand Up @@ -91,6 +94,7 @@ export class LanguageClient extends EventTarget implements AbstractLanguageClien
*/
const response = await this.sendRequest(InitializeRequest.type, {
capabilities: this.clientCapabilities,
initializationOptions: this.initializationOptions,
processId: 0,
rootUri: 'browser:///',
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {
import { Connection } from 'vscode-languageserver';
import { TextDocument } from 'vscode-languageserver-textdocument';
import { ClientCapabilities } from '../ClientCapabilities';
import { percent as percent, Progress } from '../progress';
import { percent, Progress } from '../progress';

export type AugmentedSourceCode<SCT extends SourceCodeType = SourceCodeType> = SourceCode<SCT> & {
textDocument: TextDocument;
Expand Down Expand Up @@ -131,7 +131,7 @@ export class DocumentManager {
const filesToLoad = await recursiveReadDirectory(
this.fs,
rootUri,
([uri]) => /.(liquid|json)$/.test(uri) && !this.sourceCodes.has(uri),
([uri]) => /\.(liquid|json)$/.test(uri) && !this.sourceCodes.has(uri),
);

progress.report(10, 'Preloading files');
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { RenameFilesParams } from 'vscode-languageserver-protocol';
import { AugmentedSourceCode } from '../documents';

export interface BaseRenameHandler {
onDidRenameFiles(params: RenameFilesParams, theme: AugmentedSourceCode[]): Promise<void>;
onDidRenameFiles(params: RenameFilesParams): Promise<void>;
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,21 +20,18 @@ export class RenameHandler {
constructor(
connection: Connection,
capabilities: ClientCapabilities,
private documentManager: DocumentManager,
private findThemeRootURI: (uri: string) => Promise<string>,
documentManager: DocumentManager,
findThemeRootURI: (uri: string) => Promise<string>,
) {
this.handlers = [
new SnippetRenameHandler(connection, capabilities),
new AssetRenameHandler(connection, capabilities),
new SnippetRenameHandler(documentManager, connection, capabilities, findThemeRootURI),
new AssetRenameHandler(documentManager, connection, capabilities, findThemeRootURI),
];
}

async onDidRenameFiles(params: RenameFilesParams) {
try {
const rootUri = await this.findThemeRootURI(path.dirname(params.files[0].oldUri));
await this.documentManager.preload(rootUri);
const theme = this.documentManager.theme(rootUri, true);
const promises = this.handlers.map((handler) => handler.onDidRenameFiles(params, theme));
const promises = this.handlers.map((handler) => handler.onDidRenameFiles(params));
await Promise.all(promises);
} catch (error) {
console.error(error);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { LiquidVariable, NodeTypes } from '@shopify/liquid-html-parser';
import { SourceCodeType } from '@shopify/theme-check-common';
import { path, SourceCodeType, visit } from '@shopify/theme-check-common';
import { Connection } from 'vscode-languageserver';
import {
ApplyWorkspaceEditRequest,
Expand All @@ -9,9 +9,8 @@ import {
WorkspaceEdit,
} from 'vscode-languageserver-protocol';
import { ClientCapabilities } from '../../ClientCapabilities';
import { AugmentedLiquidSourceCode, AugmentedSourceCode } from '../../documents';
import { AugmentedLiquidSourceCode, AugmentedSourceCode, DocumentManager } from '../../documents';
import { assetName, isAsset } from '../../utils/uri';
import { visit } from '@shopify/theme-check-common';
import { BaseRenameHandler } from '../BaseRenameHandler';

/**
Expand All @@ -27,18 +26,29 @@ import { BaseRenameHandler } from '../BaseRenameHandler';
* WorkspaceEdit that changes the references to the new asset.
*/
export class AssetRenameHandler implements BaseRenameHandler {
constructor(private connection: Connection, private capabilities: ClientCapabilities) {}
constructor(
private documentManager: DocumentManager,
private connection: Connection,
private capabilities: ClientCapabilities,
private findThemeRootURI: (uri: string) => Promise<string>,
) {}

async onDidRenameFiles(params: RenameFilesParams, theme: AugmentedSourceCode[]): Promise<void> {
async onDidRenameFiles(params: RenameFilesParams): Promise<void> {
if (!this.capabilities.hasApplyEditSupport) return;
const isLiquidSourceCode = (file: AugmentedSourceCode): file is AugmentedLiquidSourceCode =>
file.type === SourceCodeType.LiquidHtml;

const liquidSourceCodes = theme.filter(isLiquidSourceCode);
const relevantRenames = params.files.filter(
(file) => isAsset(file.oldUri) && isAsset(file.newUri),
);

// Only preload if you have something to do
if (relevantRenames.length === 0) return;
const rootUri = await this.findThemeRootURI(path.dirname(params.files[0].oldUri));
await this.documentManager.preload(rootUri);
const theme = this.documentManager.theme(rootUri, true);
const liquidSourceCodes = theme.filter(isLiquidSourceCode);

const promises = relevantRenames.map(async (file) => {
const oldAssetName = assetName(file.oldUri);
const newAssetName = assetName(file.newUri);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { LiquidTag, NamedTags, NodeTypes } from '@shopify/liquid-html-parser';
import { SourceCodeType } from '@shopify/theme-check-common';
import { path, SourceCodeType, visit } from '@shopify/theme-check-common';
import { Connection } from 'vscode-languageserver';
import {
ApplyWorkspaceEditRequest,
Expand All @@ -8,11 +8,10 @@ import {
TextEdit,
WorkspaceEdit,
} from 'vscode-languageserver-protocol';
import { AugmentedLiquidSourceCode, AugmentedSourceCode } from '../../documents';
import { ClientCapabilities } from '../../ClientCapabilities';
import { AugmentedLiquidSourceCode, AugmentedSourceCode, DocumentManager } from '../../documents';
import { isSnippet, snippetName } from '../../utils/uri';
import { visit } from '@shopify/theme-check-common';
import { BaseRenameHandler } from '../BaseRenameHandler';
import { ClientCapabilities } from '../../ClientCapabilities';

/**
* The SnippetRenameHandler will handle snippet renames.
Expand All @@ -27,18 +26,29 @@ import { ClientCapabilities } from '../../ClientCapabilities';
* WorkspaceEdit that changes the references to the new snippet.
*/
export class SnippetRenameHandler implements BaseRenameHandler {
constructor(private connection: Connection, private capabilities: ClientCapabilities) {}
constructor(
private documentManager: DocumentManager,
private connection: Connection,
private capabilities: ClientCapabilities,
private findThemeRootURI: (uri: string) => Promise<string>,
) {}

async onDidRenameFiles(params: RenameFilesParams, theme: AugmentedSourceCode[]): Promise<void> {
async onDidRenameFiles(params: RenameFilesParams): Promise<void> {
if (!this.capabilities.hasApplyEditSupport) return;
const isLiquidSourceCode = (file: AugmentedSourceCode): file is AugmentedLiquidSourceCode =>
file.type === SourceCodeType.LiquidHtml;

const liquidSourceCodes = theme.filter(isLiquidSourceCode);
const relevantRenames = params.files.filter(
(file) => isSnippet(file.oldUri) && isSnippet(file.newUri),
);

// Only preload if you have something to do
if (relevantRenames.length === 0) return;
const rootUri = await this.findThemeRootURI(path.dirname(params.files[0].oldUri));
await this.documentManager.preload(rootUri);
const theme = this.documentManager.theme(rootUri, true);
const liquidSourceCodes = theme.filter(isLiquidSourceCode);

const promises = relevantRenames.map(async (file) => {
const oldSnippetName = snippetName(file.oldUri);
const newSnippetName = snippetName(file.newUri);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,19 @@ import { ClientCapabilities } from '../ClientCapabilities';
export const CHECK_ON_OPEN = 'themeCheck.checkOnOpen' as const;
export const CHECK_ON_SAVE = 'themeCheck.checkOnSave' as const;
export const CHECK_ON_CHANGE = 'themeCheck.checkOnChange' as const;
export const ConfigurationKeys = [CHECK_ON_OPEN, CHECK_ON_SAVE, CHECK_ON_CHANGE] as const;
export const PRELOAD_ON_BOOT = 'themeCheck.preloadOnBoot' as const;
export const ConfigurationKeys = [
CHECK_ON_OPEN,
CHECK_ON_SAVE,
CHECK_ON_CHANGE,
PRELOAD_ON_BOOT,
] as const;

export class Configuration {
[CHECK_ON_OPEN]: boolean = true;
[CHECK_ON_SAVE]: boolean = true;
[CHECK_ON_CHANGE]: boolean = true;
[PRELOAD_ON_BOOT]: boolean = true;

constructor(private connection: Connection, private capabilities: ClientCapabilities) {
this.connection = connection;
Expand All @@ -26,6 +33,7 @@ export class Configuration {
this[CHECK_ON_OPEN] = this.capabilities.initializationOption(CHECK_ON_OPEN, true);
this[CHECK_ON_SAVE] = this.capabilities.initializationOption(CHECK_ON_SAVE, true);
this[CHECK_ON_CHANGE] = this.capabilities.initializationOption(CHECK_ON_CHANGE, true);
this[PRELOAD_ON_BOOT] = this.capabilities.initializationOption(PRELOAD_ON_BOOT, true);
}

async shouldCheckOnOpen() {
Expand All @@ -43,6 +51,11 @@ export class Configuration {
return this[CHECK_ON_CHANGE];
}

async shouldPreloadOnBoot() {
await this.fetchConfiguration();
return this[PRELOAD_ON_BOOT];
}

clearCache() {
this.fetchConfiguration.clearCache();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -320,17 +320,18 @@ export function startServer(
// fast when you eventually need it.
//
// I'm choosing the textDocument/didOpen notification as a hook because
// I'm not sure we have a better solution that this. Yes we have the
// I'm not sure we have a better solution than this. Yes we have the
// initialize request with the workspace folders, but you might have opened
// an app folder. The root of a theme app extension would probably be
// at ${workspaceRoot}/extensions/${appExtensionName}. It'd be hard to
// figure out from the initialize request params.
//
// If we open a file that we know is liquid, then we can kind of guarantee
// we'll find a theme root and we'll preload that.
findThemeRootURI(uri)
.then((rootUri) => documentManager.preload(rootUri))
.catch((e) => console.error(e));
if (await configuration.shouldPreloadOnBoot()) {
const rootUri = await findThemeRootURI(uri);
documentManager.preload(rootUri);
}
});

connection.onDidChangeTextDocument(async (params) => {
Expand Down

0 comments on commit 0b7534b

Please sign in to comment.