Skip to content

Commit 012c402

Browse files
authored
Merge pull request #71 from cedricziel/string-path-references
Add support for `EXT:foo/bar/..` syntax
2 parents 359b201 + a336f3a commit 012c402

File tree

3 files changed

+204
-0
lines changed

3 files changed

+204
-0
lines changed
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
package com.cedricziel.idea.typo3.annotation;
2+
3+
import com.intellij.lang.annotation.AnnotationHolder;
4+
import com.intellij.lang.annotation.Annotator;
5+
import com.intellij.openapi.vfs.VirtualFile;
6+
import com.intellij.psi.PsiElement;
7+
import com.intellij.psi.PsiFile;
8+
import com.intellij.psi.search.FilenameIndex;
9+
import com.intellij.psi.search.GlobalSearchScope;
10+
import com.jetbrains.php.lang.psi.elements.StringLiteralExpression;
11+
import org.codehaus.plexus.util.DirectoryScanner;
12+
import org.jetbrains.annotations.NotNull;
13+
14+
import java.util.Arrays;
15+
import java.util.List;
16+
17+
/**
18+
* Matches {@link StringLiteralExpression} elements and annotates them if a resource does not exist.
19+
* <p>
20+
* Example strings:
21+
* "EXT:foo/bar/baz.typoscript"
22+
*/
23+
public class PathResourceAnnotator implements Annotator {
24+
@Override
25+
public void annotate(@NotNull PsiElement element, @NotNull AnnotationHolder holder) {
26+
if (!(element instanceof StringLiteralExpression)) {
27+
return;
28+
}
29+
30+
String content = ((StringLiteralExpression) element).getContents();
31+
if (!content.startsWith("EXT:") || element.getProject().getBasePath() == null) {
32+
return;
33+
}
34+
35+
String resourceName = content.substring(4, content.length());
36+
if (resourceName.contains(":")) {
37+
// resource name points to a sub-resource such as a translation string, not here.
38+
return;
39+
}
40+
41+
String[] split = resourceName.split("/");
42+
String fileName = split[split.length - 1];
43+
DirectoryScanner scanner = new DirectoryScanner();
44+
45+
scanner.setBasedir(element.getProject().getBasePath());
46+
scanner.addDefaultExcludes();
47+
scanner.setIncludes(new String[]{"**/" + resourceName + "/"});
48+
scanner.scan();
49+
50+
String[] paths = scanner.getIncludedFiles();
51+
if (paths.length != 0) {
52+
// the resource is a directory
53+
return;
54+
}
55+
56+
List<PsiFile> filesByName = Arrays.asList(FilenameIndex.getFilesByName(element.getProject(), fileName, GlobalSearchScope.allScope(element.getProject())));
57+
if (filesByName.size() == 0) {
58+
createErrorMessage(element, holder, resourceName);
59+
60+
return;
61+
}
62+
63+
for (PsiFile file : filesByName) {
64+
VirtualFile virtualFile = file.getVirtualFile();
65+
66+
67+
if (virtualFile.getPath().contains("typo3conf/ext/" + resourceName) || virtualFile.getPath().contains("sysext/" + resourceName)) {
68+
// all good
69+
return;
70+
}
71+
72+
if (virtualFile.isDirectory() && virtualFile.getPath().endsWith(resourceName)) {
73+
return;
74+
}
75+
}
76+
77+
createErrorMessage(element, holder, resourceName);
78+
}
79+
80+
private void createErrorMessage(@NotNull PsiElement element, @NotNull AnnotationHolder holder, String resourceName) {
81+
String message = "Resource \"%s\" could not be found in your current project.".replace("%s", resourceName);
82+
holder.createErrorAnnotation(element, message);
83+
}
84+
}
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
package com.cedricziel.idea.typo3.codeInsight;
2+
3+
import com.intellij.codeInsight.completion.*;
4+
import com.intellij.codeInsight.lookup.LookupElement;
5+
import com.intellij.codeInsight.lookup.LookupElementPresentation;
6+
import com.intellij.icons.AllIcons;
7+
import com.intellij.openapi.project.Project;
8+
import com.intellij.patterns.PlatformPatterns;
9+
import com.intellij.psi.PsiElement;
10+
import com.intellij.util.ProcessingContext;
11+
import com.jetbrains.php.lang.psi.elements.StringLiteralExpression;
12+
import org.codehaus.plexus.util.DirectoryScanner;
13+
import org.jetbrains.annotations.NotNull;
14+
15+
import java.util.Arrays;
16+
17+
public class PathResourceCompletionContributor extends CompletionContributor {
18+
public PathResourceCompletionContributor() {
19+
extend(
20+
CompletionType.BASIC,
21+
PlatformPatterns.psiElement().withParent(PlatformPatterns.psiElement(StringLiteralExpression.class)),
22+
new CompletionProvider<CompletionParameters>() {
23+
@Override
24+
protected void addCompletions(@NotNull CompletionParameters parameters, ProcessingContext context, @NotNull CompletionResultSet result) {
25+
PsiElement position = parameters.getPosition();
26+
27+
Project project = position.getProject();
28+
PsiElement parent = position.getParent();
29+
if (!(parent instanceof StringLiteralExpression)) {
30+
return;
31+
}
32+
33+
String currentText = parent.getText();
34+
if (project.getBasePath() == null || !currentText.contains("EXT:")) {
35+
return;
36+
}
37+
38+
String[] includes;
39+
if (currentText.contains("/")) {
40+
String current = currentText
41+
.split("/", 1)[0]
42+
.replace("EXT:", "")
43+
.replace("IntellijIdeaRulezzz", "")
44+
.replace("'", "")
45+
.trim();
46+
47+
includes = new String[]{
48+
"**/sysext/" + current + "**",
49+
"**/typo3conf/ext/" + current + "**",
50+
};
51+
} else {
52+
includes = new String[]{
53+
"**/sysext/*/",
54+
"**/typo3conf/ext/*/",
55+
};
56+
}
57+
58+
DirectoryScanner scanner = new DirectoryScanner();
59+
60+
scanner.setBasedir(project.getBasePath());
61+
scanner.addDefaultExcludes();
62+
scanner.setIncludes(includes);
63+
scanner.scan();
64+
65+
Arrays.stream(scanner.getIncludedFiles()).forEach(f -> {
66+
result.addElement(new LookupElement() {
67+
@NotNull
68+
@Override
69+
public String getLookupString() {
70+
String[] splitted = f.split("sysext/");
71+
if (splitted.length > 1) {
72+
return "EXT:" + splitted[1];
73+
}
74+
75+
splitted = f.split("typo3conf/ext/");
76+
if (splitted.length > 1) {
77+
return "EXT:" + splitted[1];
78+
}
79+
80+
return "EXT:" + f;
81+
}
82+
});
83+
});
84+
85+
Arrays.stream(scanner.getIncludedDirectories()).forEach(f -> {
86+
result.addElement(new LookupElement() {
87+
88+
@Override
89+
public void renderElement(LookupElementPresentation presentation) {
90+
presentation.setIcon(AllIcons.Modules.SourceFolder);
91+
92+
super.renderElement(presentation);
93+
}
94+
95+
@NotNull
96+
@Override
97+
public String getLookupString() {
98+
String[] splitted = f.split("sysext/");
99+
if (splitted.length > 1) {
100+
return "EXT:" + splitted[1];
101+
}
102+
103+
splitted = f.split("typo3conf/ext/");
104+
if (splitted.length > 1) {
105+
return "EXT:" + splitted[1];
106+
}
107+
108+
return "EXT:" + f;
109+
}
110+
});
111+
});
112+
}
113+
}
114+
);
115+
}
116+
}

src/main/resources/META-INF/plugin.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -271,10 +271,14 @@ It is a great inspiration for possible solutions and parts of the code.</p>
271271
implementationClass="com.cedricziel.idea.typo3.persistence.codeInsight.DoctrineTablesCompletionContributor"/>
272272
<completion.contributor language="PHP"
273273
implementationClass="com.cedricziel.idea.typo3.tca.codeInsight.TCACompletionContributor"/>
274+
<completion.contributor language="PHP"
275+
implementationClass="com.cedricziel.idea.typo3.codeInsight.PathResourceCompletionContributor"/>
276+
274277

275278
<!-- annotation -->
276279
<annotator language="PHP" implementationClass="com.cedricziel.idea.typo3.annotation.IconAnnotator"/>
277280
<annotator language="PHP" implementationClass="com.cedricziel.idea.typo3.annotation.RouteAnnotator"/>
281+
<annotator language="PHP" implementationClass="com.cedricziel.idea.typo3.annotation.PathResourceAnnotator"/>
278282

279283
<!-- marker -->
280284
<codeInsight.lineMarkerProvider language="PHP"

0 commit comments

Comments
 (0)