Skip to content

Commit fafe31d

Browse files
committed
feat: theme definitions and hovers
1 parent 50b8ec4 commit fafe31d

File tree

10 files changed

+167
-1
lines changed

10 files changed

+167
-1
lines changed

package.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,11 @@
6767
"type": "boolean",
6868
"default": true,
6969
"description": "Enable hover decorations for Magento 2 XML files."
70+
},
71+
"magento-toolbox.provideThemeDefinitions": {
72+
"type": "boolean",
73+
"default": true,
74+
"description": "Enable definitions for Magento 2 theme files."
7075
}
7176
}
7277
},

src/definition/XmlDefinitionProviderProcessor.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { XmlSuggestionProviderProcessor } from 'common/xml/XmlSuggestionProvider
99
import { AclDefinitionProvider } from './xml/AclDefinitionProvider';
1010
import { ModuleDefinitionProvider } from './xml/ModuleDefinitionProvider';
1111
import { TemplateDefinitionProvider } from './xml/TemplateDefinitionProvider';
12+
import { ThemeDefinitionProvider } from './xml/ThemeDefinitionProvider';
1213

1314
export class XmlDefinitionProviderProcessor
1415
extends XmlSuggestionProviderProcessor<LocationLink>
@@ -19,6 +20,7 @@ export class XmlDefinitionProviderProcessor
1920
new AclDefinitionProvider(),
2021
new ModuleDefinitionProvider(),
2122
new TemplateDefinitionProvider(),
23+
new ThemeDefinitionProvider(),
2224
]);
2325
}
2426

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import { LocationLink, Uri, Range, TextDocument } from 'vscode';
2+
import IndexManager from 'indexer/IndexManager';
3+
import { XmlSuggestionProvider, CombinedCondition } from 'common/xml/XmlSuggestionProvider';
4+
import { XMLElement, XMLAttribute } from '@xml-tools/ast';
5+
import { ElementNameMatches } from 'common/xml/suggestion/condition/ElementNameMatches';
6+
import ThemeIndexer from 'indexer/theme/ThemeIndexer';
7+
8+
export class ThemeDefinitionProvider extends XmlSuggestionProvider<LocationLink> {
9+
public getFilePatterns(): string[] {
10+
return ['**/theme.xml'];
11+
}
12+
13+
public getElementContentMatches(): CombinedCondition[] {
14+
return [[new ElementNameMatches('parent')]];
15+
}
16+
17+
public getConfigKey(): string | undefined {
18+
return 'provideThemeDefinitions';
19+
}
20+
21+
public getSuggestionItems(
22+
value: string,
23+
range: Range,
24+
document: TextDocument,
25+
element: XMLElement,
26+
attribute?: XMLAttribute
27+
): LocationLink[] {
28+
const themeIndexData = IndexManager.getIndexData(ThemeIndexer.KEY);
29+
30+
if (!themeIndexData) {
31+
return [];
32+
}
33+
34+
const theme = themeIndexData.getThemeById(value);
35+
36+
if (!theme) {
37+
return [];
38+
}
39+
40+
return [
41+
{
42+
targetUri: Uri.file(theme.path),
43+
targetRange: new Range(0, 0, 0, 0),
44+
originSelectionRange: range,
45+
},
46+
];
47+
}
48+
}

src/hover/XmlHoverProviderProcessor.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,16 @@ import { XmlSuggestionProviderProcessor } from 'common/xml/XmlSuggestionProvider
33
import { AclHoverProvider } from 'hover/xml/AclHoverProvider';
44
import { ModuleHoverProvider } from 'hover/xml/ModuleHoverProvider';
55
import { CronHoverProvider } from 'hover/xml/CronHoverProvider';
6+
import { ThemeHoverProvider } from 'hover/xml/ThemeHoverProvider';
67

78
export class XmlHoverProviderProcessor extends XmlSuggestionProviderProcessor<Hover> {
89
public constructor() {
9-
super([new AclHoverProvider(), new ModuleHoverProvider(), new CronHoverProvider()]);
10+
super([
11+
new AclHoverProvider(),
12+
new ModuleHoverProvider(),
13+
new CronHoverProvider(),
14+
new ThemeHoverProvider(),
15+
]);
1016
}
1117

1218
public async provideHover(
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import { Hover, MarkdownString, Uri, Range, workspace, TextDocument } from 'vscode';
2+
import IndexManager from 'indexer/IndexManager';
3+
import { CombinedCondition, XmlSuggestionProvider } from 'common/xml/XmlSuggestionProvider';
4+
import { ElementNameMatches } from 'common/xml/suggestion/condition/ElementNameMatches';
5+
import { XMLElement, XMLAttribute } from '@xml-tools/ast';
6+
import ThemeIndexer from 'indexer/theme/ThemeIndexer';
7+
import path from 'path';
8+
9+
export class ThemeHoverProvider extends XmlSuggestionProvider<Hover> {
10+
public getElementContentMatches(): CombinedCondition[] {
11+
return [[new ElementNameMatches('parent')]];
12+
}
13+
14+
public getConfigKey(): string | undefined {
15+
return 'provideXmlHovers';
16+
}
17+
18+
public getFilePatterns(): string[] {
19+
return ['**/theme.xml'];
20+
}
21+
22+
public getSuggestionItems(
23+
value: string,
24+
range: Range,
25+
document: TextDocument,
26+
element: XMLElement,
27+
attribute?: XMLAttribute
28+
): Hover[] {
29+
const workspaceFolder = workspace.getWorkspaceFolder(document.uri);
30+
31+
if (!workspaceFolder) {
32+
return [];
33+
}
34+
35+
const themeIndexData = IndexManager.getIndexData(ThemeIndexer.KEY);
36+
37+
if (!themeIndexData) {
38+
return [];
39+
}
40+
41+
const theme = themeIndexData.getThemeById(value);
42+
43+
if (!theme) {
44+
return [];
45+
}
46+
47+
const markdown = new MarkdownString();
48+
markdown.appendMarkdown(`**Theme**: ${theme.title}\n\n`);
49+
markdown.appendMarkdown(`- ID: \`${theme.id}\`\n\n`);
50+
51+
const relativePath = path.relative(workspaceFolder.uri.fsPath, theme.path);
52+
53+
markdown.appendMarkdown(`- Path: \`${relativePath}\`\n\n`);
54+
55+
if (theme.parent) {
56+
markdown.appendMarkdown(`- Parent: \n\n - ${theme.parent}\n\n`);
57+
}
58+
59+
markdown.appendMarkdown(`[theme.xml](${Uri.file(theme.path)})`);
60+
61+
return [new Hover(markdown, range)];
62+
}
63+
}

src/indexer/IndexManager.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,10 @@ class IndexManager {
116116

117117
await Promise.all(
118118
batch.map(async file => {
119+
if (!indexer.canIndex(file)) {
120+
return;
121+
}
122+
119123
const data = await indexer.indexFile(file);
120124

121125
if (data !== undefined) {

src/indexer/Indexer.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ export abstract class Indexer<D = any> {
55
public abstract getId(): IndexerKey;
66
public abstract getName(): string;
77
public abstract getPattern(uri: Uri): GlobPattern;
8+
public canIndex(uri: Uri): boolean {
9+
return true;
10+
}
811
public abstract indexFile(uri: Uri): Promise<D | undefined>;
912
public abstract getVersion(): number;
1013
}

src/indexer/theme/ThemeIndexData.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,8 @@ export class ThemeIndexData extends AbstractIndexData<Theme> {
1717
return normalized.startsWith(base);
1818
});
1919
}
20+
21+
public getThemeById(id: string): Theme | undefined {
22+
return this.getThemes().find(theme => theme.id.toLowerCase() === id.toLowerCase());
23+
}
2024
}

src/indexer/theme/ThemeIndexer.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,17 @@ export default class ThemeIndexer extends Indexer<Theme> {
3636
return new RelativePattern(uri, '**/theme.xml');
3737
}
3838

39+
public canIndex(uri: Uri): boolean {
40+
return !uri.fsPath.includes('Test/Unit') && !uri.fsPath.includes('dev/tests');
41+
}
42+
3943
public async indexFile(uri: Uri): Promise<Theme | undefined> {
44+
const id = await this.getThemeId(uri);
45+
46+
if (!id) {
47+
return undefined;
48+
}
49+
4050
const xml = await FileSystem.readFile(uri);
4151
const parsed = this.xmlParser.parse(xml);
4252

@@ -52,11 +62,31 @@ export default class ThemeIndexer extends Indexer<Theme> {
5262

5363
const theme: Theme = {
5464
title,
65+
id,
5566
path: uri.fsPath,
5667
basePath: Uri.joinPath(uri, '..').fsPath,
5768
parent: typeof parent === 'string' ? parent : undefined,
5869
};
5970

6071
return theme;
6172
}
73+
74+
private async getThemeId(uri: Uri): Promise<string | undefined> {
75+
const registrationUri = Uri.joinPath(uri, '../registration.php');
76+
77+
let registration: string | undefined;
78+
try {
79+
registration = await FileSystem.readFile(registrationUri);
80+
} catch (error) {
81+
return undefined;
82+
}
83+
84+
const themeId = registration.match(/'frontend\/(.+)'/);
85+
86+
if (!themeId) {
87+
return undefined;
88+
}
89+
90+
return themeId[1];
91+
}
6292
}

src/indexer/theme/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
export interface Theme {
22
title: string;
3+
id: string;
34
path: string;
45
basePath: string;
56
parent?: string;

0 commit comments

Comments
 (0)