Skip to content

Commit e70dce7

Browse files
committed
feat: jump to definition in XML files; optimization
1 parent 249915f commit e70dce7

File tree

9 files changed

+167
-29
lines changed

9 files changed

+167
-29
lines changed

src/common/Context.ts

Lines changed: 2 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
1-
import PhpParser from 'parser/php/Parser';
21
import { commands, TextEditor } from 'vscode';
3-
import { PhpFile } from 'parser/php/PhpFile';
4-
import DocumentCache from 'cache/DocumentCache';
2+
import PhpDocumentParser from './php/PhpDocumentParser';
53

64
export interface EditorContext {
75
canGeneratePlugin: boolean;
@@ -44,7 +42,7 @@ class Context {
4442
return false;
4543
}
4644

47-
const phpFile = await this.getParsedPhpFile(editor);
45+
const phpFile = await PhpDocumentParser.parse(editor.document);
4846
const phpClass = phpFile.classes[0];
4947

5048
if (phpClass) {
@@ -71,19 +69,6 @@ class Context {
7169

7270
return false;
7371
}
74-
75-
private async getParsedPhpFile(editor: TextEditor): Promise<PhpFile> {
76-
const cacheKey = `php-file`;
77-
78-
if (DocumentCache.has(editor.document, cacheKey)) {
79-
return DocumentCache.get(editor.document, cacheKey);
80-
}
81-
82-
const phpParser = new PhpParser();
83-
const phpFile = await phpParser.parseDocument(editor.document);
84-
DocumentCache.set(editor.document, cacheKey, phpFile);
85-
return phpFile;
86-
}
8772
}
8873

8974
export default new Context();

src/common/php/ClasslikeInfo.ts

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ import { PhpClass } from 'parser/php/PhpClass';
22
import { PhpFile } from 'parser/php/PhpFile';
33
import { PhpInterface } from 'parser/php/PhpInterface';
44
import { PhpMethod } from 'parser/php/PhpMethod';
5-
import Position from 'util/Position';
6-
import { Range } from 'vscode';
5+
import PositionUtil from 'util/Position';
6+
import { Position, Range } from 'vscode';
77

88
export class ClasslikeInfo {
99
public constructor(private readonly phpFile: PhpFile) {}
@@ -21,12 +21,10 @@ export class ClasslikeInfo {
2121
return;
2222
}
2323

24-
const range = new Range(
25-
Position.phpAstPositionToVsCodePosition(classlikeName.loc.start),
26-
Position.phpAstPositionToVsCodePosition(classlikeName.loc.end)
24+
return new Range(
25+
PositionUtil.phpAstPositionToVsCodePosition(classlikeName.loc.start),
26+
PositionUtil.phpAstPositionToVsCodePosition(classlikeName.loc.end)
2727
);
28-
29-
return range;
3028
}
3129

3230
public getMethodByName(
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import DocumentCache from 'cache/DocumentCache';
2+
import PhpParser from 'parser/php/Parser';
3+
import { PhpFile } from 'parser/php/PhpFile';
4+
import { TextDocument, Uri } from 'vscode';
5+
6+
class PhpDocumentParser {
7+
protected readonly parser: PhpParser;
8+
9+
constructor() {
10+
this.parser = new PhpParser();
11+
}
12+
13+
public async parse(document: TextDocument): Promise<PhpFile> {
14+
const cacheKey = `php-file`;
15+
16+
if (DocumentCache.has(document, cacheKey)) {
17+
return DocumentCache.get(document, cacheKey);
18+
}
19+
20+
const phpParser = new PhpParser();
21+
const phpFile = await phpParser.parseDocument(document);
22+
DocumentCache.set(document, cacheKey, phpFile);
23+
return phpFile;
24+
}
25+
26+
public async parseUri(document: TextDocument, uri: Uri): Promise<PhpFile> {
27+
const cacheKey = `php-file-${uri.fsPath}`;
28+
29+
if (DocumentCache.has(document, cacheKey)) {
30+
return DocumentCache.get(document, cacheKey);
31+
}
32+
33+
const phpParser = new PhpParser();
34+
const phpFile = await phpParser.parse(uri);
35+
DocumentCache.set(document, cacheKey, phpFile);
36+
return phpFile;
37+
}
38+
}
39+
40+
export default new PhpDocumentParser();
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
import { ClasslikeInfo } from 'common/php/ClasslikeInfo';
2+
import PhpDocumentParser from 'common/php/PhpDocumentParser';
3+
import PhpNamespace from 'common/PhpNamespace';
4+
import { AutoloadNamespaceIndexData } from 'indexer/autoload-namespace/AutoloadNamespaceIndexData';
5+
import AutoloadNamespaceIndexer from 'indexer/autoload-namespace/AutoloadNamespaceIndexer';
6+
import IndexManager from 'indexer/IndexManager';
7+
import {
8+
DefinitionProvider,
9+
TextDocument,
10+
Position,
11+
CancellationToken,
12+
LocationLink,
13+
Uri,
14+
Range,
15+
} from 'vscode';
16+
17+
export class XmlClasslikeDefinitionProvider implements DefinitionProvider {
18+
private namespaceIndexData: AutoloadNamespaceIndexData | undefined;
19+
20+
public async provideDefinition(
21+
document: TextDocument,
22+
position: Position,
23+
token: CancellationToken
24+
) {
25+
const range = document.getWordRangeAtPosition(position, /"[^"]*"/);
26+
27+
if (!range) {
28+
return null;
29+
}
30+
31+
const word = document.getText(range);
32+
33+
const namespaceIndexData = this.getNamespaceIndexData();
34+
35+
if (!namespaceIndexData) {
36+
return null;
37+
}
38+
39+
const potentialNamespace = word.replace(/"/g, '');
40+
41+
const classUri = await namespaceIndexData.findClassByNamespace(
42+
PhpNamespace.fromString(potentialNamespace)
43+
);
44+
45+
if (!classUri) {
46+
return null;
47+
}
48+
49+
const targetPosition = await this.getClasslikeNameRange(document, classUri);
50+
51+
return [
52+
{
53+
targetUri: classUri,
54+
targetRange: targetPosition,
55+
originSelectionRange: range,
56+
} as LocationLink,
57+
];
58+
}
59+
60+
private getNamespaceIndexData(): AutoloadNamespaceIndexData | undefined {
61+
if (!this.namespaceIndexData) {
62+
const namespaceIndex = IndexManager.getIndexData(AutoloadNamespaceIndexer.KEY);
63+
64+
if (!namespaceIndex) {
65+
return undefined;
66+
}
67+
68+
this.namespaceIndexData = new AutoloadNamespaceIndexData(namespaceIndex);
69+
}
70+
71+
return this.namespaceIndexData;
72+
}
73+
74+
private async getClasslikeNameRange(
75+
document: TextDocument,
76+
classUri: Uri
77+
): Promise<Position | Range> {
78+
const phpFile = await PhpDocumentParser.parseUri(document, classUri);
79+
const classLikeInfo = new ClasslikeInfo(phpFile);
80+
const range = classLikeInfo.getNameRange();
81+
82+
if (!range) {
83+
return new Position(0, 0);
84+
}
85+
86+
return range;
87+
}
88+
}

src/extension.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import DiagnosticCollectionProvider from 'diagnostics/DiagnosticCollectionProvid
1010
import ChangeTextEditorSelectionObserver from 'observer/ChangeTextEditorSelectionObserver';
1111
import DocumentCache from 'cache/DocumentCache';
1212
import GenerateContextPluginCommand from 'command/GenerateContextPluginCommand';
13+
import { XmlClasslikeDefinitionProvider } from 'definition/XmlClasslikeDefinitionProvider';
1314
// This method is called when your extension is activated
1415
// Your extension is activated the very first time the command is executed
1516
export async function activate(context: vscode.ExtensionContext) {
@@ -66,6 +67,10 @@ export async function activate(context: vscode.ExtensionContext) {
6667
})
6768
);
6869

70+
context.subscriptions.push(
71+
vscode.languages.registerDefinitionProvider('xml', new XmlClasslikeDefinitionProvider())
72+
);
73+
6974
await activeTextEditorChangeObserver.execute(vscode.window.activeTextEditor);
7075

7176
if (vscode.window.activeTextEditor) {

src/indexer/IndexManager.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import DiIndexer from './di/DiIndexer';
66
import IndexStorage from './IndexStorage';
77
import ModuleIndexer from './module/ModuleIndexer';
88
import AutoloadNamespaceIndexer from './autoload-namespace/AutoloadNamespaceIndexer';
9+
import { clear } from 'typescript-memoize';
910

1011
class IndexManager {
1112
protected indexers: Indexer[] = [];
@@ -69,6 +70,8 @@ class IndexManager {
6970

7071
this.indexStorage.set(indexer.getId(), indexData);
7172

73+
clear([indexer.getId()]);
74+
7275
Common.stopStopwatch(timer);
7376

7477
progress.report({ increment: 100 });
@@ -119,6 +122,8 @@ class IndexManager {
119122
indexData.set(file.fsPath, data);
120123
}
121124
}
125+
126+
clear([indexer.getId()]);
122127
}
123128

124129
protected shouldIndex(index: Indexer): boolean {

src/indexer/autoload-namespace/AutoloadNamespaceIndexData.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,14 @@ import PhpNamespace from 'common/PhpNamespace';
33
import FileSystem from 'util/FileSystem';
44
import { IndexData } from 'indexer/IndexData';
55
import { AutoloadNamespaceData } from './types';
6+
import { Memoize } from 'typescript-memoize';
7+
import AutoloadNamespaceIndexer from './AutoloadNamespaceIndexer';
68

79
export class AutoloadNamespaceIndexData extends IndexData<AutoloadNamespaceData> {
10+
@Memoize({
11+
tags: [AutoloadNamespaceIndexer.KEY],
12+
hashFunction: (namespace: PhpNamespace) => namespace.toString(),
13+
})
814
public async findClassByNamespace(namespace: PhpNamespace): Promise<Uri | undefined> {
915
const parts = namespace.getParts();
1016

src/indexer/autoload-namespace/AutoloadNamespaceIndexer.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ export default class AutoloadNamespaceIndexer extends Indexer<AutoloadNamespaceD
4040
for (const [namespace, paths] of Object.entries(composer.autoload['psr-4'])) {
4141
const directories = Array.isArray(paths) ? paths : [paths];
4242

43-
data[namespace] = directories.map(
43+
data[this.normalizeNamespace(namespace)] = directories.map(
4444
(dir: string) => Uri.joinPath(baseDir, dir.replace(/^\.\//, '')).fsPath
4545
);
4646
}
@@ -51,12 +51,16 @@ export default class AutoloadNamespaceIndexer extends Indexer<AutoloadNamespaceD
5151
for (const [namespace, paths] of Object.entries(composer.autoload['psr-0'])) {
5252
const directories = Array.isArray(paths) ? paths : [paths];
5353

54-
data[namespace] = directories.map(
54+
data[this.normalizeNamespace(namespace)] = directories.map(
5555
(dir: string) => Uri.joinPath(baseDir, dir.replace(/^\.\//, '')).fsPath
5656
);
5757
}
5858
}
5959

6060
return data;
6161
}
62+
63+
private normalizeNamespace(namespace: string): string {
64+
return namespace.replace(/\\$/, '');
65+
}
6266
}

src/indexer/di/DiIndexData.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,26 @@
11
import { Memoize } from 'typescript-memoize';
22
import { DiData, DiPlugin, DiPreference, DiType, DiVirtualType } from './types';
33
import { IndexData } from 'indexer/IndexData';
4+
import DiIndexer from './DiIndexer';
45

56
export class DiIndexData extends IndexData<DiData> {
6-
@Memoize()
7+
@Memoize({
8+
tags: [DiIndexer.KEY],
9+
})
710
public getTypes(): DiType[] {
811
return this.getValues().flatMap(data => data.types);
912
}
1013

11-
@Memoize()
14+
@Memoize({
15+
tags: [DiIndexer.KEY],
16+
})
1217
public getPreferences(): DiPreference[] {
1318
return this.getValues().flatMap(data => data.preferences);
1419
}
1520

16-
@Memoize()
21+
@Memoize({
22+
tags: [DiIndexer.KEY],
23+
})
1724
public getVirtualTypes(): DiVirtualType[] {
1825
return this.getValues().flatMap(data => data.virtualTypes);
1926
}

0 commit comments

Comments
 (0)