Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,13 @@ All notable changes to the "magento-toolbox" extension will be documented in thi

Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how to structure this file.

## [Unreleased]
- Added: Class namespace autocomplete in XML files
- Added: Module name autocomplete in module.xml files
- Added: Added extension config fields for enabling/disabling completions, definitions and hovers
- Added: Index data persistance
- Changed: Adjusted namespace indexer logic

## [1.4.0] - 2025-04-04
- Added: Generator command for a ViewModel class
- Added: Generator command for data patches
Expand Down
26 changes: 26 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,20 @@
"type": "git",
"url": "https://github.com/magebitcom/magento-toolbox.git"
},
"homepage": "https://github.com/magebitcom/magento-toolbox",
"bugs": {
"url": "https://github.com/magebitcom/magento-toolbox/issues"
},
"categories": [
"Other"
],
"keywords": [
"magento",
"adobe commerce",
"code completion",
"code generation",
"intellisense"
],
"activationEvents": [
"workspaceContains:**/app/etc/env.php",
"workspaceContains:**/app/etc/di.xml",
Expand All @@ -41,6 +52,21 @@
"editPresentation": "multilineText",
"default": "",
"markdownDescription": "`%module%` will be replaced with the module name. \n\n **Do not add comment symbols like `<!--` or `-->`, they will be added automatically.**"
},
"magento-toolbox.provideXmlCompletions": {
"type": "boolean",
"default": true,
"description": "Enable autocomplete for Magento 2 XML files."
},
"magento-toolbox.provideXmlDefinitions": {
"type": "boolean",
"default": true,
"description": "Enable definitions for Magento 2 XML files."
},
"magento-toolbox.provideXmlHovers": {
"type": "boolean",
"default": true,
"description": "Enable hover decorations for Magento 2 XML files."
}
}
},
Expand Down
2 changes: 1 addition & 1 deletion src/command/IndexWorkspaceCommand.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,6 @@ export default class IndexWorkspaceCommand extends Command {
}

public async execute(...args: any[]): Promise<void> {
await IndexRunner.indexWorkspace();
await IndexRunner.indexWorkspace(true);
}
}
11 changes: 11 additions & 0 deletions src/common/Config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { workspace } from 'vscode';

class Config {
public readonly SECTION = 'magento-toolbox';

public get<T = string>(key: string): T | undefined {
return workspace.getConfiguration(this.SECTION).get<T>(key);
}
}

export default new Config();
12 changes: 12 additions & 0 deletions src/common/PhpNamespace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,22 @@ export default class PhpNamespace {
return new PhpNamespace(parts);
}

public pop(): string {
return this.parts.pop() as string;
}

public getParts(): string[] {
return this.parts;
}

public getHead(): string {
return this.parts[0];
}

public getTail(): string {
return this.parts[this.parts.length - 1];
}

public toString(): string {
return this.parts.join(PhpNamespace.NS_SEPARATOR);
}
Expand Down
6 changes: 2 additions & 4 deletions src/common/php/FileHeader.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
import { workspace } from 'vscode';
import Config from 'common/Config';

export default class FileHeader {
public static getHeader(module: string): string | undefined {
const header = workspace
.getConfiguration('magento-toolbox')
.get<string>('phpFileHeaderComment');
const header = Config.get<string>('phpFileHeaderComment');

if (!header) {
return undefined;
Expand Down
6 changes: 2 additions & 4 deletions src/common/xml/FileHeader.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
import { workspace } from 'vscode';
import Config from 'common/Config';

export default class FileHeader {
public static getHeader(module: string): string | undefined {
const header = workspace
.getConfiguration('magento-toolbox')
.get<string>('xmlFileHeaderComment');
const header = Config.get<string>('xmlFileHeaderComment');

if (!header) {
return undefined;
Expand Down
10 changes: 7 additions & 3 deletions src/common/xml/XmlDocumentParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,17 +18,21 @@ class XmlDocumentParser {
this.parser = new PhpParser();
}

public async parse(document: TextDocument): Promise<TokenData> {
public async parse(document: TextDocument, skipCache = false): Promise<TokenData> {
const cacheKey = `xml-file`;

if (DocumentCache.has(document, cacheKey)) {
if (!skipCache && DocumentCache.has(document, cacheKey)) {
return DocumentCache.get(document, cacheKey);
}

const { cst, tokenVector } = parse(document.getText());
const ast = buildAst(cst as DocumentCstNode, tokenVector);
const tokenData: TokenData = { cst: cst as DocumentCstNode, tokenVector, ast };
DocumentCache.set(document, cacheKey, tokenData);

if (!skipCache) {
DocumentCache.set(document, cacheKey, tokenData);
}

return tokenData;
}
}
Expand Down
57 changes: 57 additions & 0 deletions src/completion/XmlCompletionProvider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { minimatch } from 'minimatch';
import {
CancellationToken,
CompletionItem,
CompletionItemProvider,
CompletionList,
Position,
TextDocument,
Range,
} from 'vscode';
import { getSuggestions, SuggestionProviders } from '@xml-tools/content-assist';
import XmlDocumentParser, { TokenData } from 'common/xml/XmlDocumentParser';
import Config from 'common/Config';
import { ModuleCompletionItemProvider } from './xml/ModuleCompletionItemProvider';
import { NamespaceCompletionItemProvider } from './xml/NamespaceCompletionItemProvider';
import { XmlCompletionItemProvider } from './xml/XmlCompletionItemProvider';

export class XmlCompletionProvider implements CompletionItemProvider {
private readonly providers: XmlCompletionItemProvider[];

public constructor() {
this.providers = [new ModuleCompletionItemProvider(), new NamespaceCompletionItemProvider()];
}

public async provideCompletionItems(
document: TextDocument,
position: Position,
token: CancellationToken
): Promise<CompletionItem[]> {
if (!this.providers.some(provider => provider.canProvideCompletion(document))) {
return [];
}

const tokenData = await XmlDocumentParser.parse(document, true);

const providerCompletionItems = await Promise.all(
this.providers.map(provider =>
this.getProviderCompletionItems(provider, document, position, tokenData)
)
);

return providerCompletionItems.flat();
}

private async getProviderCompletionItems(
provider: XmlCompletionItemProvider,
document: TextDocument,
position: Position,
tokenData: TokenData
): Promise<CompletionItem[]> {
if (!provider.canProvideCompletion(document)) {
return [];
}

return provider.getCompletions(document, position, tokenData);
}
}
51 changes: 51 additions & 0 deletions src/completion/xml/ModuleCompletionItemProvider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { CompletionItem, CompletionItemKind } from 'vscode';
import { SuggestionProviders } from '@xml-tools/content-assist';
import IndexManager from 'indexer/IndexManager';
import ModuleIndexer from 'indexer/module/ModuleIndexer';
import { XMLElement, XMLAttribute } from '@xml-tools/ast';
import { XmlCompletionItemProvider } from './XmlCompletionItemProvider';

export class ModuleCompletionItemProvider extends XmlCompletionItemProvider {
getFilePatterns(): string[] {
return ['**/etc/module.xml'];
}

getCompletionProviders(): SuggestionProviders<CompletionItem> {
return {
attributeValue: [this.getAttributeValueCompletions.bind(this)],
};
}

private getAttributeValueCompletions({
element,
attribute,
}: {
element: XMLElement;
attribute: XMLAttribute;
}): CompletionItem[] {
if (
element.name !== 'module' ||
(element.parent as XMLElement)?.name !== 'sequence' ||
attribute.key !== 'name'
) {
return [];
}

const value = attribute?.value || '';
return this.getCompletionItems(value);
}

private getCompletionItems(prefix: string): CompletionItem[] {
const moduleIndexData = IndexManager.getIndexData(ModuleIndexer.KEY);

if (!moduleIndexData) {
return [];
}

const completions = moduleIndexData.getModulesByPrefix(prefix);

return completions.map(module => {
return new CompletionItem(module.name, CompletionItemKind.Value);
});
}
}
Loading