Skip to content

Commit cf7346b

Browse files
committed
feat: template indexer, definitions and autocomplete
1 parent 782f231 commit cf7346b

File tree

12 files changed

+280
-71
lines changed

12 files changed

+280
-71
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how
1010
- Added: Module hover information
1111
- Added: Added extension config fields for enabling/disabling completions, definitions and hovers
1212
- Added: acl.xml indexer, definitions, autocomplete and hovers
13+
- Added: template file indexer, definitions and autocomplete
1314
- Added: Index data persistance
1415
- Changed: Adjusted namespace indexer logic
1516

Lines changed: 3 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -1,82 +1,20 @@
11
import { Command } from 'command/Command';
2-
import IndexManager from 'indexer/IndexManager';
3-
import ModuleIndexer from 'indexer/module/ModuleIndexer';
2+
import GetMagentoPath from 'common/GetMagentoPath';
43
import { Uri, window, env } from 'vscode';
54

65
export default class CopyMagentoPathCommand extends Command {
7-
public static readonly TEMPLATE_EXTENSIONS = ['.phtml'];
8-
public static readonly WEB_EXTENSIONS = ['.css', '.js'];
9-
public static readonly IMAGE_EXTENSIONS = ['.png', '.jpg', '.jpeg', '.gif', '.svg'];
10-
11-
private static readonly TEMPLATE_PATHS = [
12-
'view/frontend/templates/',
13-
'view/adminhtml/templates/',
14-
'view/base/templates/',
15-
'templates/',
16-
];
17-
18-
private static readonly WEB_PATHS = [
19-
'view/frontend/web/',
20-
'view/adminhtml/web/',
21-
'view/base/web/',
22-
'web/',
23-
];
24-
256
constructor() {
267
super('magento-toolbox.copyMagentoPath');
278
}
289

2910
public async execute(file: Uri): Promise<void> {
30-
if (!file) {
31-
return;
32-
}
33-
34-
const moduleIndexData = IndexManager.getIndexData(ModuleIndexer.KEY);
35-
36-
if (!moduleIndexData) {
37-
return;
38-
}
39-
40-
const module = moduleIndexData.getModuleByUri(file);
41-
42-
if (!module) {
43-
return;
44-
}
11+
const magentoPath = GetMagentoPath.getMagentoPath(file);
4512

46-
const paths = this.getPaths(file);
47-
48-
if (!paths) {
13+
if (!magentoPath) {
4914
return;
5015
}
5116

52-
const pathIndex = paths.findIndex(p => file.fsPath.lastIndexOf(p) !== -1);
53-
54-
if (pathIndex === -1) {
55-
return;
56-
}
57-
58-
const endIndex = file.fsPath.lastIndexOf(paths[pathIndex]);
59-
const offset = paths[pathIndex].length;
60-
const relativePath = file.fsPath.slice(offset + endIndex);
61-
62-
const magentoPath = `${module.name}::${relativePath}`;
63-
6417
await env.clipboard.writeText(magentoPath);
6518
window.showInformationMessage(`Copied: ${magentoPath}`);
6619
}
67-
68-
private getPaths(file: Uri): string[] | undefined {
69-
if (CopyMagentoPathCommand.TEMPLATE_EXTENSIONS.some(ext => file.fsPath.endsWith(ext))) {
70-
return CopyMagentoPathCommand.TEMPLATE_PATHS;
71-
}
72-
73-
if (
74-
CopyMagentoPathCommand.WEB_EXTENSIONS.some(ext => file.fsPath.endsWith(ext)) ||
75-
CopyMagentoPathCommand.IMAGE_EXTENSIONS.some(ext => file.fsPath.endsWith(ext))
76-
) {
77-
return CopyMagentoPathCommand.WEB_PATHS;
78-
}
79-
80-
return undefined;
81-
}
8220
}

src/common/Context.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { commands, TextEditor } from 'vscode';
22
import PhpDocumentParser from './php/PhpDocumentParser';
3-
import CopyMagentoPathCommand from 'command/CopyMagentoPathCommand';
3+
import GetMagentoPath from './GetMagentoPath';
44

55
export interface EditorContext {
66
canGeneratePlugin: boolean;
@@ -47,9 +47,9 @@ class Context {
4747
canGeneratePlugin: false,
4848
canGeneratePreference: false,
4949
supportedMagentoPathExtensions: [
50-
...CopyMagentoPathCommand.TEMPLATE_EXTENSIONS,
51-
...CopyMagentoPathCommand.WEB_EXTENSIONS,
52-
...CopyMagentoPathCommand.IMAGE_EXTENSIONS,
50+
...GetMagentoPath.TEMPLATE_EXTENSIONS,
51+
...GetMagentoPath.WEB_EXTENSIONS,
52+
...GetMagentoPath.IMAGE_EXTENSIONS,
5353
],
5454
};
5555
}

src/common/GetMagentoPath.ts

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
import IndexManager from 'indexer/IndexManager';
2+
import ModuleIndexer from 'indexer/module/ModuleIndexer';
3+
import { Uri } from 'vscode';
4+
5+
export class GetMagentoPath {
6+
public readonly TEMPLATE_EXTENSIONS = ['.phtml'];
7+
public readonly WEB_EXTENSIONS = ['.css', '.js'];
8+
public readonly IMAGE_EXTENSIONS = ['.png', '.jpg', '.jpeg', '.gif', '.svg'];
9+
10+
public readonly TEMPLATE_PATHS = [
11+
'view/frontend/templates/',
12+
'view/adminhtml/templates/',
13+
'view/base/templates/',
14+
'templates/',
15+
];
16+
17+
public readonly WEB_PATHS = [
18+
'view/frontend/web/',
19+
'view/adminhtml/web/',
20+
'view/base/web/',
21+
'web/',
22+
];
23+
24+
public getMagentoPath(file: Uri): string | undefined {
25+
if (!file) {
26+
return;
27+
}
28+
29+
const moduleIndexData = IndexManager.getIndexData(ModuleIndexer.KEY);
30+
31+
if (!moduleIndexData) {
32+
return;
33+
}
34+
35+
const module = moduleIndexData.getModuleByUri(file, false);
36+
37+
if (!module) {
38+
return;
39+
}
40+
41+
const paths = this.getPaths(file);
42+
43+
if (!paths) {
44+
return;
45+
}
46+
47+
const pathIndex = paths.findIndex(p => file.fsPath.lastIndexOf(p) !== -1);
48+
49+
if (pathIndex === -1) {
50+
return;
51+
}
52+
53+
const endIndex = file.fsPath.lastIndexOf(paths[pathIndex]);
54+
const offset = paths[pathIndex].length;
55+
const relativePath = file.fsPath.slice(offset + endIndex);
56+
57+
const magentoPath = `${module.name}::${relativePath}`;
58+
59+
return magentoPath;
60+
}
61+
62+
private getPaths(file: Uri): string[] | undefined {
63+
if (this.TEMPLATE_EXTENSIONS.some(ext => file.fsPath.endsWith(ext))) {
64+
return this.TEMPLATE_PATHS;
65+
}
66+
67+
if (
68+
this.WEB_EXTENSIONS.some(ext => file.fsPath.endsWith(ext)) ||
69+
this.IMAGE_EXTENSIONS.some(ext => file.fsPath.endsWith(ext))
70+
) {
71+
return this.WEB_PATHS;
72+
}
73+
74+
return undefined;
75+
}
76+
}
77+
78+
export default new GetMagentoPath();

src/completion/XmlCompletionProviderProcessor.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { CancellationToken } from 'vscode';
77
import { ModuleCompletionProvider } from './xml/ModuleCompletionProvider';
88
import { NamespaceCompletionProvider } from './xml/NamespaceCompletionProvider';
99
import { AclCompletionProvider } from './xml/AclCompletionProvider';
10+
import { TemplateCompletionProvider } from './xml/TemplateCompletionProvider';
1011

1112
export class XmlCompletionProviderProcessor
1213
extends XmlSuggestionProviderProcessor<CompletionItem>
@@ -17,6 +18,7 @@ export class XmlCompletionProviderProcessor
1718
new ModuleCompletionProvider(),
1819
new NamespaceCompletionProvider(),
1920
new AclCompletionProvider(),
21+
new TemplateCompletionProvider(),
2022
]);
2123
}
2224

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import { CompletionItem, 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 { AttributeNameMatches } from 'common/xml/suggestion/condition/AttributeNameMatches';
6+
import TemplateIndexer from 'indexer/template/TemplateIndexer';
7+
import { ElementAttributeMatches } from 'common/xml/suggestion/condition/ElementAttributeMatches';
8+
9+
export class TemplateCompletionProvider extends XmlSuggestionProvider<CompletionItem> {
10+
public getFilePatterns(): string[] {
11+
return ['**/view/**/layout/*.xml', '**/etc/**/di.xml'];
12+
}
13+
14+
public getAttributeValueConditions(): CombinedCondition[] {
15+
return [[new AttributeNameMatches('template')]];
16+
}
17+
18+
public getElementContentMatches(): CombinedCondition[] {
19+
return [
20+
[
21+
new ElementAttributeMatches('name', 'template'),
22+
new ElementAttributeMatches('xsi:type', 'string'),
23+
],
24+
];
25+
}
26+
27+
public getConfigKey(): string | undefined {
28+
return 'provideXmlDefinitions';
29+
}
30+
31+
public getSuggestionItems(
32+
value: string,
33+
range: Range,
34+
document: TextDocument,
35+
element: XMLElement,
36+
attribute?: XMLAttribute
37+
): CompletionItem[] {
38+
const templateIndexData = IndexManager.getIndexData(TemplateIndexer.KEY);
39+
40+
if (!templateIndexData) {
41+
return [];
42+
}
43+
44+
const templates = templateIndexData.getTemplatesByPrefix(value);
45+
46+
return templates.map(template => {
47+
const item = new CompletionItem(template.magentoPath);
48+
item.range = range;
49+
return item;
50+
});
51+
}
52+
}

src/definition/XmlDefinitionProviderProcessor.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,18 @@ import {
88
import { XmlSuggestionProviderProcessor } from 'common/xml/XmlSuggestionProviderProcessor';
99
import { AclDefinitionProvider } from './xml/AclDefinitionProvider';
1010
import { ModuleDefinitionProvider } from './xml/ModuleDefinitionProvider';
11+
import { TemplateDefinitionProvider } from './xml/TemplateDefinitionProvider';
1112

1213
export class XmlDefinitionProviderProcessor
1314
extends XmlSuggestionProviderProcessor<LocationLink>
1415
implements DefinitionProvider
1516
{
1617
public constructor() {
17-
super([new AclDefinitionProvider(), new ModuleDefinitionProvider()]);
18+
super([
19+
new AclDefinitionProvider(),
20+
new ModuleDefinitionProvider(),
21+
new TemplateDefinitionProvider(),
22+
]);
1823
}
1924

2025
public async provideDefinition(
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
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 { AttributeNameMatches } from 'common/xml/suggestion/condition/AttributeNameMatches';
6+
import TemplateIndexer from 'indexer/template/TemplateIndexer';
7+
import { ElementAttributeMatches } from 'common/xml/suggestion/condition/ElementAttributeMatches';
8+
9+
export class TemplateDefinitionProvider extends XmlSuggestionProvider<LocationLink> {
10+
public getFilePatterns(): string[] {
11+
return ['**/view/**/layout/*.xml', '**/etc/**/di.xml'];
12+
}
13+
14+
public getAttributeValueConditions(): CombinedCondition[] {
15+
return [[new AttributeNameMatches('template')]];
16+
}
17+
18+
public getElementContentMatches(): CombinedCondition[] {
19+
return [
20+
[
21+
new ElementAttributeMatches('name', 'template'),
22+
new ElementAttributeMatches('xsi:type', 'string'),
23+
],
24+
];
25+
}
26+
27+
public getConfigKey(): string | undefined {
28+
return 'provideXmlDefinitions';
29+
}
30+
31+
public getSuggestionItems(
32+
value: string,
33+
range: Range,
34+
document: TextDocument,
35+
element: XMLElement,
36+
attribute?: XMLAttribute
37+
): LocationLink[] {
38+
const templateIndexData = IndexManager.getIndexData(TemplateIndexer.KEY);
39+
40+
if (!templateIndexData) {
41+
return [];
42+
}
43+
44+
const templates = templateIndexData.getTemplatesByPrefix(value);
45+
46+
if (!templates.length) {
47+
return [];
48+
}
49+
50+
return templates.map(template => {
51+
const templateUri = Uri.file(template.path);
52+
53+
return {
54+
targetUri: templateUri,
55+
targetRange: new Range(0, 0, 0, 0),
56+
originSelectionRange: range,
57+
};
58+
});
59+
}
60+
}

src/indexer/IndexManager.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,20 +16,24 @@ import Logger from 'util/Logger';
1616
import { IndexerKey } from 'types/indexer';
1717
import AclIndexer from './acl/AclIndexer';
1818
import { AclIndexData } from './acl/AclIndexData';
19+
import TemplateIndexer from './template/TemplateIndexer';
20+
import { TemplateIndexData } from './template/TemplateIndexData';
1921

2022
type IndexerInstance =
2123
| DiIndexer
2224
| ModuleIndexer
2325
| AutoloadNamespaceIndexer
2426
| EventsIndexer
25-
| AclIndexer;
27+
| AclIndexer
28+
| TemplateIndexer;
2629

2730
type IndexerDataMap = {
2831
[DiIndexer.KEY]: DiIndexData;
2932
[ModuleIndexer.KEY]: ModuleIndexData;
3033
[AutoloadNamespaceIndexer.KEY]: AutoloadNamespaceIndexData;
3134
[EventsIndexer.KEY]: EventsIndexData;
3235
[AclIndexer.KEY]: AclIndexData;
36+
[TemplateIndexer.KEY]: TemplateIndexData;
3337
};
3438

3539
class IndexManager {
@@ -43,6 +47,7 @@ class IndexManager {
4347
new AutoloadNamespaceIndexer(),
4448
new EventsIndexer(),
4549
new AclIndexer(),
50+
new TemplateIndexer(),
4651
];
4752
this.indexStorage = new IndexStorage();
4853
}
@@ -173,6 +178,9 @@ class IndexManager {
173178
case AclIndexer.KEY:
174179
return new AclIndexData(data) as IndexerDataMap[T];
175180

181+
case TemplateIndexer.KEY:
182+
return new TemplateIndexData(data) as IndexerDataMap[T];
183+
176184
default:
177185
return undefined;
178186
}

0 commit comments

Comments
 (0)