diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 706c88fdb4e5..1ad289e13abc 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -2420,6 +2420,9 @@ jobs: - name: php.atoum run: ant $OPTS -f php/php.atoum test + - name: php.blade + run: ant $OPTS -f php/php.blade test + - name: php.code.analysis run: ant $OPTS -f php/php.code.analysis test diff --git a/.gitignore b/.gitignore index 50efd8725540..f95dc9941f23 100644 --- a/.gitignore +++ b/.gitignore @@ -119,5 +119,10 @@ derby.log /webcommon/javascript2.json/src/org/netbeans/modules/javascript2/json/parser/Json*.java +php/php.blade/src/org/netbeans/modules/php/blade/syntax/antlr4/v10/BladeAntlr*.java +php/php.blade/src/org/netbeans/modules/php/blade/syntax/antlr4/formatter/BladeAntlr*.java +php/php.blade/src/org/netbeans/modules/php/blade/syntax/antlr4/html_components/Blade*.java +php/php.blade/src/org/netbeans/modules/php/blade/syntax/antlr4/php/Blade*.java + # idea .idea diff --git a/nbbuild/cluster.properties b/nbbuild/cluster.properties index bbd52bc8c877..b458e00e2da9 100644 --- a/nbbuild/cluster.properties +++ b/nbbuild/cluster.properties @@ -933,6 +933,7 @@ nb.cluster.php=\ php.api.testing,\ php.apigen,\ php.atoum,\ + php.blade,\ php.code.analysis,\ php.codeception,\ php.composer,\ diff --git a/php/php.api.editor/nbproject/project.xml b/php/php.api.editor/nbproject/project.xml index bd14af04a19d..db2c8ae3d2c5 100644 --- a/php/php.api.editor/nbproject/project.xml +++ b/php/php.api.editor/nbproject/project.xml @@ -75,6 +75,7 @@ org.nbphpcouncil.modules.php.yii2 org.netbeans.modules.php.api.framework org.netbeans.modules.php.api.testing + org.netbeans.modules.php.blade org.netbeans.modules.php.atoum org.netbeans.modules.php.cake3 org.netbeans.modules.php.codeception diff --git a/php/php.api.framework/nbproject/project.xml b/php/php.api.framework/nbproject/project.xml index 8a6164e1c807..4dcdf5329791 100644 --- a/php/php.api.framework/nbproject/project.xml +++ b/php/php.api.framework/nbproject/project.xml @@ -163,6 +163,7 @@ org.nbphpcouncil.modules.php.laravel4 org.nbphpcouncil.modules.php.yii org.nbphpcouncil.modules.php.yii2 + org.netbeans.modules.php.blade org.netbeans.modules.php.cake3 org.netbeans.modules.php.doctrine2 org.netbeans.modules.php.fuel diff --git a/php/php.api.phpmodule/nbproject/project.xml b/php/php.api.phpmodule/nbproject/project.xml index 09738895b338..d269125fd2af 100644 --- a/php/php.api.phpmodule/nbproject/project.xml +++ b/php/php.api.phpmodule/nbproject/project.xml @@ -239,6 +239,7 @@ org.netbeans.modules.php.api.testing org.netbeans.modules.php.apigen org.netbeans.modules.php.atoum + org.netbeans.modules.php.blade org.netbeans.modules.php.cake3 org.netbeans.modules.php.code.analysis org.netbeans.modules.php.codeception diff --git a/php/php.blade/TODO.md b/php/php.blade/TODO.md new file mode 100644 index 000000000000..3eba6338a698 --- /dev/null +++ b/php/php.blade/TODO.md @@ -0,0 +1,14 @@ +# TODO for module + +## Sync update from plugin +- [ ] Hyperlinkimpl +- [ ] new Directives +- [ ] Plugin system (inertia) + +## Include in module / link to documentation? + +- [ ] Option controller + +## Cleanup / simplification + +- [ ] ?? \ No newline at end of file diff --git a/php/php.blade/build.xml b/php/php.blade/build.xml new file mode 100644 index 000000000000..3f674b8483b4 --- /dev/null +++ b/php/php.blade/build.xml @@ -0,0 +1,85 @@ + + + + Builds, tests, and runs the project org.netbeans.modules.php.blade + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/php/php.blade/licenseinfo.xml b/php/php.blade/licenseinfo.xml new file mode 100644 index 000000000000..d9ccbc4a7538 --- /dev/null +++ b/php/php.blade/licenseinfo.xml @@ -0,0 +1,39 @@ + + + + + src/org/netbeans/modules/php/blade/resources/emptyBladeFile.blade.php + src/org/netbeans/modules/php/blade/resources/formattingExample.blade.php + src/org/netbeans/modules/php/blade/resources/highlightBlade.blade.php + src/org/netbeans/modules/php/blade/resources/NewBladeFileDescription.html + + + + + src/org/netbeans/modules/php/blade/resources/icon.png + src/org/netbeans/modules/php/blade/resources/icons/at.png + src/org/netbeans/modules/php/blade/resources/icons/blade_file.png + src/org/netbeans/modules/php/blade/resources/icons/layout.png + + + + diff --git a/php/php.blade/manifest.mf b/php/php.blade/manifest.mf new file mode 100644 index 000000000000..7340e66f38d2 --- /dev/null +++ b/php/php.blade/manifest.mf @@ -0,0 +1,7 @@ +Manifest-Version: 1.0 +AutoUpdate-Show-In-Client: true +OpenIDE-Module: org.netbeans.modules.php.blade/1 +OpenIDE-Module-Layer: org/netbeans/modules/php/blade/resources/layer.xml +OpenIDE-Module-Localizing-Bundle: org/netbeans/modules/php/blade/resources/Bundle.properties +OpenIDE-Module-Specification-Version: 1.0.0 + diff --git a/php/php.blade/nbproject/project.properties b/php/php.blade/nbproject/project.properties new file mode 100644 index 000000000000..4c3b5d6a165f --- /dev/null +++ b/php/php.blade/nbproject/project.properties @@ -0,0 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +javac.compilerargs=-Xlint -Xlint:-serial +spec.version.base.fatal.warning=false +javac.release=17 diff --git a/php/php.blade/nbproject/project.xml b/php/php.blade/nbproject/project.xml new file mode 100644 index 000000000000..96180d2c4deb --- /dev/null +++ b/php/php.blade/nbproject/project.xml @@ -0,0 +1,464 @@ + + + + org.netbeans.modules.apisupport.project + + + org.netbeans.modules.php.blade + + + org.netbeans.api.annotations.common + + + + 1 + 1.48 + + + + org.netbeans.api.templates + + + + 1.28 + + + + org.netbeans.core.multiview + + + + 1 + 1.64 + + + + org.netbeans.libs.antlr4.runtime + + + + 2 + 1.21.0.1 + + + + org.netbeans.modules.csl.api + + + + 2 + 2.78.0.2.1.1.8.1 + + + + org.netbeans.modules.csl.types + + + + 1 + 1.20 + + + + org.netbeans.modules.editor + + + + 3 + 1.107.0.6.3.23.55 + + + + org.netbeans.modules.editor.bracesmatching + + + + 0 + 1.59.0.55 + + + + org.netbeans.modules.editor.codetemplates + + + + 1 + 1.64.0.1 + + + + org.netbeans.modules.editor.completion + + + + 1 + 1.65.0.2 + + + + org.netbeans.modules.editor.document + + + + 1.29.0.3 + + + + org.netbeans.modules.editor.indent + + + + 2 + 1.63 + + + + org.netbeans.modules.editor.lib + + + + 3 + 4.27.0.23.3.55 + + + + org.netbeans.modules.editor.lib2 + + + + 1 + 2.40.0.55.3 + + + + org.netbeans.modules.editor.mimelookup + + + + 1 + 1.60 + + + + org.netbeans.modules.editor.settings + + + + 1 + 1.77 + + + + org.netbeans.modules.editor.util + + + + 1 + 1.85 + + + + org.netbeans.modules.html.lexer + + + + 1 + 1.58 + + + + org.netbeans.modules.lexer + + + + 2 + 1.83.0.1 + + + + org.netbeans.modules.lexer.antlr4 + + + + 1.1.0.1 + + + + org.netbeans.modules.nbjunit + + + + 1 + 1.110 + + + + org.netbeans.modules.options.api + + + + 1 + 1.65 + + + + org.netbeans.modules.parsing.api + + + + 1 + 9.27.0.8 + + + + org.netbeans.modules.parsing.indexing + + + + 9.29.0.1.3.8 + + + + org.netbeans.modules.php.api.editor + + + + 0 + 0.52 + + + + org.netbeans.modules.php.api.phpmodule + + + + 2.97 + + + + org.netbeans.modules.php.editor + + + + 2 + + + + + org.netbeans.modules.projectapi + + + + 1 + 1.91 + + + + org.netbeans.modules.projectuiapi + + + + 1 + 1.109.0.8 + + + + org.netbeans.modules.projectuiapi.base + + + + 1 + 1.106.0.9 + + + + org.netbeans.modules.refactoring.api + + + + 1.67.0.1 + + + + org.openide.awt + + + + 7.88 + + + + org.openide.dialogs + + + + 7.66 + + + + org.openide.filesystems + + + + 9.33 + + + + org.openide.filesystems.nb + + + + 9.30 + + + + org.openide.loaders + + + + 7.90 + + + + org.openide.nodes + + + + 7.65 + + + + org.openide.text + + + + 6.88 + + + + org.openide.util + + + + 9.28 + + + + org.openide.util.lookup + + + + 8.54 + + + + org.openide.util.ui + + + + 9.28 + + + + org.openide.windows + + + + 6.97 + + + + + + unit + + org.netbeans.libs.junit4 + + + + org.netbeans.modules.csl.api + + + + + + org.netbeans.modules.editor.bracesmatching + + + + + org.netbeans.modules.editor.lib + + + + org.netbeans.modules.editor.mimelookup + + + + + org.netbeans.modules.editor.mimelookup.impl + + + org.netbeans.modules.editor.util + + + + + org.netbeans.modules.lexer + + + + + org.netbeans.modules.lexer.nbbridge + + + + org.netbeans.modules.nbjunit + + + + + org.openide.util.lookup + + + + + org.netbeans.modules.projectapi.nb + + + org.netbeans.modules.parsing.nb + + + org.netbeans.modules.parsing.api + + + + org.netbeans.modules.nbjunit + + + + + + + + + diff --git a/php/php.blade/src/org/netbeans/modules/php/blade/csl/elements/ClassElement.java b/php/php.blade/src/org/netbeans/modules/php/blade/csl/elements/ClassElement.java new file mode 100644 index 000000000000..5e4669a32332 --- /dev/null +++ b/php/php.blade/src/org/netbeans/modules/php/blade/csl/elements/ClassElement.java @@ -0,0 +1,51 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.php.blade.csl.elements; + +import org.netbeans.api.annotations.common.CheckForNull; +import org.netbeans.api.annotations.common.NullAllowed; +import org.netbeans.modules.csl.api.ElementKind; +import org.openide.filesystems.FileObject; + +public class ClassElement extends NamedElement { + + @NullAllowed + private final String namespace; + + public ClassElement(String name, FileObject file) { + super(name, file, ElementType.PHP_CLASS); + this.namespace = null; + } + + public ClassElement(String name, String namespace, + FileObject file) { + super(name, file, ElementType.PHP_CLASS); + this.namespace = namespace; + } + + @Override + public ElementKind getKind() { + return ElementKind.CLASS; + } + + @CheckForNull + public String getNamespace() { + return namespace; + } +} diff --git a/php/php.blade/src/org/netbeans/modules/php/blade/csl/elements/DirectiveElement.java b/php/php.blade/src/org/netbeans/modules/php/blade/csl/elements/DirectiveElement.java new file mode 100644 index 000000000000..056293183ba2 --- /dev/null +++ b/php/php.blade/src/org/netbeans/modules/php/blade/csl/elements/DirectiveElement.java @@ -0,0 +1,34 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.php.blade.csl.elements; + +import org.netbeans.modules.csl.api.ElementKind; +import org.openide.filesystems.FileObject; + +public class DirectiveElement extends NamedElement { + + public DirectiveElement(String name, FileObject file) { + super(name, file, ElementType.DIRECTIVE); + } + + @Override + public ElementKind getKind() { + return ElementKind.METHOD; + } +} diff --git a/php/php.blade/src/org/netbeans/modules/php/blade/csl/elements/ElementType.java b/php/php.blade/src/org/netbeans/modules/php/blade/csl/elements/ElementType.java new file mode 100644 index 000000000000..bf387448b326 --- /dev/null +++ b/php/php.blade/src/org/netbeans/modules/php/blade/csl/elements/ElementType.java @@ -0,0 +1,35 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.php.blade.csl.elements; + +public enum ElementType { + NA, + CUSTOM_DIRECTIVE, + PATH, + DIRECTIVE, + VARIABLE, + PHP_CLASS, + PHP_NAMESPACE, + PHP_FUNCTION, + PHP_CONSTANT, + LARAVEL_COMPONENT, + STACK_ID, + YIELD_ID, + ASSET_FILE +} diff --git a/php/php.blade/src/org/netbeans/modules/php/blade/csl/elements/NamedElement.java b/php/php.blade/src/org/netbeans/modules/php/blade/csl/elements/NamedElement.java new file mode 100644 index 000000000000..58e361c10861 --- /dev/null +++ b/php/php.blade/src/org/netbeans/modules/php/blade/csl/elements/NamedElement.java @@ -0,0 +1,112 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.php.blade.csl.elements; + +import java.util.Set; +import org.netbeans.modules.csl.api.ElementHandle; +import org.netbeans.modules.csl.api.ElementKind; +import org.netbeans.modules.csl.api.Modifier; +import org.netbeans.modules.csl.api.OffsetRange; +import org.netbeans.modules.csl.spi.ParserResult; +import static org.netbeans.modules.php.blade.csl.elements.ElementType.CUSTOM_DIRECTIVE; +import static org.netbeans.modules.php.blade.csl.elements.ElementType.STACK_ID; +import static org.netbeans.modules.php.blade.csl.elements.ElementType.YIELD_ID; +import org.netbeans.modules.php.blade.editor.BladeLanguage; +import org.openide.filesystems.FileObject; + +/** + * + * @author bhaidu + */ +public class NamedElement implements ElementHandle { + + private final String name; + private final FileObject file; + private final ElementType type; + + public NamedElement(String name, FileObject file) { + this.name = name; + this.file = file; + this.type = ElementType.NA; + } + + public NamedElement(String name, FileObject file, ElementType type) { + this.name = name; + this.file = file; + this.type = type; + } + + @Override + public FileObject getFileObject() { + return file; + } + + @Override + public String getMimeType() { + return BladeLanguage.MIME_TYPE; + } + + @Override + public String getName() { + return this.name; + } + + @Override + public String getIn() { + return ""; // NOI18N + } + + @Override + public ElementKind getKind() { + return switch (type) { + case YIELD_ID, STACK_ID, PHP_NAMESPACE, LARAVEL_COMPONENT -> + ElementKind.PACKAGE; + case CUSTOM_DIRECTIVE, DIRECTIVE, PHP_FUNCTION -> + ElementKind.METHOD; + case PHP_CLASS -> + ElementKind.CLASS; + case PHP_CONSTANT -> + ElementKind.CONSTANT; + case VARIABLE -> + ElementKind.VARIABLE; + case NA, PATH, ASSET_FILE -> + ElementKind.FILE; + }; + } + + @Override + public Set getModifiers() { + return Set.of(); + } + + @Override + public boolean signatureEquals(ElementHandle eh) { + return false; + } + + @Override + public OffsetRange getOffsetRange(ParserResult pr) { + return OffsetRange.NONE; + } + + public ElementType getType() { + return type; + } + +} diff --git a/php/php.blade/src/org/netbeans/modules/php/blade/csl/elements/PathElement.java b/php/php.blade/src/org/netbeans/modules/php/blade/csl/elements/PathElement.java new file mode 100644 index 000000000000..a6ef88a2fe83 --- /dev/null +++ b/php/php.blade/src/org/netbeans/modules/php/blade/csl/elements/PathElement.java @@ -0,0 +1,38 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.php.blade.csl.elements; + +import org.netbeans.modules.csl.api.ElementKind; +import org.openide.filesystems.FileObject; + +/** + * + * @author bhaidu + */ +public class PathElement extends NamedElement { + + public PathElement(String name, FileObject file) { + super(name, file, ElementType.PATH); + } + + @Override + public ElementKind getKind() { + return ElementKind.PACKAGE; + } +} diff --git a/php/php.blade/src/org/netbeans/modules/php/blade/csl/elements/PhpFunctionElement.java b/php/php.blade/src/org/netbeans/modules/php/blade/csl/elements/PhpFunctionElement.java new file mode 100644 index 000000000000..8763afe80d58 --- /dev/null +++ b/php/php.blade/src/org/netbeans/modules/php/blade/csl/elements/PhpFunctionElement.java @@ -0,0 +1,75 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.php.blade.csl.elements; + +import java.util.List; +import org.netbeans.api.annotations.common.CheckForNull; +import org.netbeans.api.annotations.common.NullAllowed; +import org.netbeans.modules.csl.api.ElementKind; +import org.openide.filesystems.FileObject; + +/** + * + * @author bhaidu + */ +public class PhpFunctionElement extends NamedElement { + + @NullAllowed + private final String namespace; + private final List params; + + public PhpFunctionElement(String name, FileObject file, + ElementType type, + String namespace, + List params) { + super(name, file, type); + this.namespace = namespace; + this.params = List.copyOf(params); + } + + public PhpFunctionElement(String name, FileObject file, + ElementType type, + List params) { + super(name, file, type); + this.namespace = null; + this.params = List.copyOf(params); + } + + @Override + public ElementKind getKind() { + return ElementKind.METHOD; + } + + public String getParamsAsString() { + if (params == null || params.isEmpty()) { + return "()"; // NOI18N + } + return "(" + String.join(", ", params) + ")"; // NOI18N + } + + public List getParams() { + return params; + } + + @CheckForNull + public String getNamespace() { + return namespace; + } + +} diff --git a/php/php.blade/src/org/netbeans/modules/php/blade/csl/elements/PhpKeywordElement.java b/php/php.blade/src/org/netbeans/modules/php/blade/csl/elements/PhpKeywordElement.java new file mode 100644 index 000000000000..1a580dd2a133 --- /dev/null +++ b/php/php.blade/src/org/netbeans/modules/php/blade/csl/elements/PhpKeywordElement.java @@ -0,0 +1,83 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.php.blade.csl.elements; + +import java.util.HashSet; +import java.util.Set; +import org.netbeans.modules.csl.api.ElementHandle; +import org.netbeans.modules.csl.api.ElementKind; +import org.netbeans.modules.csl.api.Modifier; +import org.netbeans.modules.csl.api.OffsetRange; +import org.netbeans.modules.csl.spi.ParserResult; +import org.netbeans.modules.php.blade.editor.BladeLanguage; +import org.openide.filesystems.FileObject; + +/** + * + * @author bogdan + */ +public class PhpKeywordElement implements ElementHandle { + + private final String name; + + public PhpKeywordElement(String name){ + this.name = name; + } + + @Override + public String getName() { + return name; + } + + @Override + public FileObject getFileObject() { + return null; + } + + @Override + public String getMimeType() { + return BladeLanguage.MIME_TYPE; + } + + @Override + public String getIn() { + return null; + } + + @Override + public ElementKind getKind() { + return ElementKind.KEYWORD; + } + + @Override + public Set getModifiers() { + return new HashSet<>(); + } + + @Override + public boolean signatureEquals(ElementHandle handle) { + return name.equals(handle.getName()); + } + + @Override + public OffsetRange getOffsetRange(ParserResult pr) { + return OffsetRange.NONE; + } + +} diff --git a/php/php.blade/src/org/netbeans/modules/php/blade/editor/BladeCommentHandler.java b/php/php.blade/src/org/netbeans/modules/php/blade/editor/BladeCommentHandler.java new file mode 100644 index 000000000000..a360ec49483c --- /dev/null +++ b/php/php.blade/src/org/netbeans/modules/php/blade/editor/BladeCommentHandler.java @@ -0,0 +1,119 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.php.blade.editor; + +import java.util.logging.Level; +import java.util.logging.Logger; +import javax.swing.text.BadLocationException; +import javax.swing.text.Document; +import org.netbeans.api.lexer.Token; +import org.netbeans.api.lexer.TokenHierarchy; +import org.netbeans.api.lexer.TokenSequence; +import org.netbeans.editor.BaseDocument; +import org.netbeans.modules.csl.spi.CommentHandler; +import org.netbeans.modules.php.blade.editor.lexer.BladeTokenId; + +/** + * known issues + * currently blade tag comment not wokring inside htmtl tags
{{ $x }}
+ * + * + * @author bhaidu + */ +public class BladeCommentHandler extends CommentHandler.DefaultCommentHandler { + + public static final String COMMENT_START_DELIMITER = "{{--"; //NOI18N + public static final String COMMENT_END_DELIMITER = "--}}"; //NOI18N + + private static final Logger LOGGER = Logger.getLogger(BladeCommentHandler.class.getName()); + + @Override + public String getCommentStartDelimiter() { + return COMMENT_START_DELIMITER; + } + + @Override + public String getCommentEndDelimiter() { + return COMMENT_END_DELIMITER; + } + + @Override + @SuppressWarnings("rawtypes") + public int[] getAdjustedBlocks(final Document doc, int from, int to) { + final int[] bounds = new int[]{from, to}; + + Runnable task = new Runnable() { + @Override + @SuppressWarnings("rawtypes") + public void run() { + TokenHierarchy th = TokenHierarchy.get(doc); + TokenSequence ts = th.tokenSequence(); + ts.move(from); + ts.moveNext(); + + Token token = ts.token(); + + if (token != null && token.id() instanceof BladeTokenId) { + //handle uncomment + switch ((BladeTokenId) token.id()) { + case BLADE_COMMENT_START -> { + bounds[0] = ts.offset(); + + while (ts.moveNext()) { + if (ts.token().id() == BladeTokenId.BLADE_COMMENT_END) { + bounds[1] = ts.offset() + ts.token().length(); + break; + } + } + } + case BLADE_DIRECTIVE -> { + bounds[0] = ts.offset(); + + //looking for directive arguments bounds + if (ts.moveNext() && ts.token().id() == BladeTokenId.PHP_BLADE_EXPRESSION) { + bounds[1] = ts.offset() + ts.token().length(); + } + + try { + //manually inserting the delimiters + doc.insertString(bounds[0], COMMENT_START_DELIMITER, null); + doc.insertString(Math.max(bounds[1], to) + COMMENT_END_DELIMITER.length(), COMMENT_END_DELIMITER, null); + bounds[0] = 0; + bounds[1] = 0; + } catch (BadLocationException ex) { + LOGGER.log(Level.WARNING, "Invalid offset: {0}", ex.offsetRequested()); // NOI18N + } + } + + } + + } + + } + }; + + if (doc instanceof BaseDocument) { + ((BaseDocument) doc).runAtomic(task); + } else { + task.run(); + } + + return new int[]{bounds[0], bounds[1]}; + } +} diff --git a/php/php.blade/src/org/netbeans/modules/php/blade/editor/BladeLanguage.java b/php/php.blade/src/org/netbeans/modules/php/blade/editor/BladeLanguage.java new file mode 100644 index 000000000000..b2a4b1b28c8e --- /dev/null +++ b/php/php.blade/src/org/netbeans/modules/php/blade/editor/BladeLanguage.java @@ -0,0 +1,221 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.php.blade.editor; + +import org.netbeans.modules.php.blade.editor.declaration.BladeDeclarationFinder; +import org.netbeans.modules.php.blade.editor.hints.BladeHintsProvider; +import org.netbeans.api.lexer.Language; +import org.netbeans.modules.parsing.spi.Parser; +import org.netbeans.modules.csl.spi.DefaultLanguageConfig; +import org.netbeans.modules.csl.spi.LanguageRegistration; +import org.netbeans.spi.lexer.Lexer; +import org.openide.util.NbBundle; +import org.netbeans.spi.lexer.LexerRestartInfo; +import org.openide.awt.ActionID; +import org.openide.awt.ActionReference; +import org.openide.awt.ActionReferences; +import org.openide.filesystems.MIMEResolver; +import org.openide.util.Lookup; +import org.openide.windows.TopComponent; +import org.netbeans.core.spi.multiview.MultiViewElement; +import org.netbeans.core.spi.multiview.text.MultiViewEditorElement; +import org.netbeans.modules.csl.api.CodeCompletionHandler; +import org.netbeans.modules.csl.api.DeclarationFinder; +import org.netbeans.modules.csl.api.Formatter; +import org.netbeans.modules.csl.api.HintsProvider; +import org.netbeans.modules.csl.api.SemanticAnalyzer; +import org.netbeans.modules.csl.api.StructureScanner; +import org.netbeans.modules.csl.spi.CommentHandler; +import static org.netbeans.modules.php.blade.editor.BladeLanguage.ACTIONS; +import org.netbeans.modules.php.blade.editor.completion.BladeCompletionHandler; +import org.netbeans.modules.php.blade.editor.format.BladeFormatter; +import org.netbeans.modules.php.blade.editor.lexer.BladeLexer; +import org.netbeans.modules.php.blade.editor.lexer.BladeTokenId; +import org.netbeans.modules.php.blade.editor.lexer.BladeTokenId.BladeLanguageHierarchy; +import org.netbeans.modules.php.blade.editor.navigator.BladeStructureScanner; +import org.netbeans.modules.php.blade.editor.parser.BladeParser; +import org.netbeans.modules.php.blade.editor.parser.BladeParserResult; +import org.netbeans.modules.php.blade.syntax.StringUtils; + +/** + * + * @author Haidu Bogdan + */ +@MIMEResolver.Registration( + resource = "../resources/mime-resolver.xml", + displayName = "#LBL_Blade_LOADER", + position = 1 +) + +@NbBundle.Messages({ + "LBL_Blade_LOADER=Blade template files" +}) +@LanguageRegistration(mimeType = BladeLanguage.MIME_TYPE, useMultiview = true) +@ActionReferences({ + @ActionReference(id = @ActionID(category = "System", id = "org.openide.actions.OpenAction"), path = ACTIONS, position = 100), + @ActionReference(id = @ActionID(category = "Edit", id = "org.openide.actions.CutAction"), path = ACTIONS, position = 300, separatorBefore = 200), + @ActionReference(id = @ActionID(category = "Edit", id = "org.openide.actions.CopyAction"), path = ACTIONS, position = 400), + @ActionReference(id = @ActionID(category = "Edit", id = "org.openide.actions.PasteAction"), path = ACTIONS, position = 500, separatorAfter = 600), + @ActionReference(id = @ActionID(category = "System", id = "org.openide.actions.NewAction"), path = ACTIONS, position = 700), + @ActionReference(id = @ActionID(category = "Edit", id = "org.openide.actions.DeleteAction"), path = ACTIONS, position = 800), + @ActionReference(id = @ActionID(category = "System", id = "org.openide.actions.RenameAction"), path = ACTIONS, position = 900, separatorAfter = 1000), + @ActionReference(id = @ActionID(category = "System", id = "org.openide.actions.SaveAsTemplateAction"), path = ACTIONS, position = 1100, separatorAfter = 1200), + @ActionReference(id = @ActionID(category = "System", id = "org.openide.actions.FileSystemAction"), path = ACTIONS, position = 1300, separatorAfter = 1400), + @ActionReference(id = @ActionID(category = "System", id = "org.openide.actions.ToolsAction"), path = ACTIONS, position = 1500), + @ActionReference(id = @ActionID(category = "System", id = "org.openide.actions.PropertiesAction"), path = ACTIONS, position = 1600), + @ActionReference(id = @ActionID(category = "TemplateActions", id = "org.netbeans.modules.php.blade.editor.actions.FindUsage"), + path = ACTIONS, separatorBefore = 1700, position = 1800), + @ActionReference(id = @ActionID(category = "System", id = "org.netbeans.modules.php.blade.editor.actions.AntlrDebug"), path = ACTIONS, position = 1900), +} +) +public class BladeLanguage extends DefaultLanguageConfig { + + public BladeLanguage() { + super(); + } + + public static final String ACTIONS = "Loaders/" + BladeLanguage.MIME_TYPE + "/Actions"; //NOI18N + public static final String MIME_TYPE = "text/x-blade"; //NOI18N + public static final String FILE_EXTENSION_SUFFIX = ".blade"; //NOI18N + public static final String FILE_EXTENSION = "blade.php"; //NOI18N + public static final String FILE_EXTENSION_WITH_DOT = StringUtils.DOT + FILE_EXTENSION; + + @Override + public Language getLexerLanguage() { + return language; + } + + @Override + public String getDisplayName() { + return "Blade"; //NOI18N + } + + @Override + public String getPreferredExtension() { + return FILE_EXTENSION; + } + + @Override + public Parser getParser() { + return new BladeParser(); + } + + //we need this to avoid lang assertion error + @Deprecated + @Override + public boolean hasStructureScanner() { + return true; + } + + @Override + public StructureScanner getStructureScanner() { + return new BladeStructureScanner(); + } + + @Override + public CodeCompletionHandler getCompletionHandler() { + return new BladeCompletionHandler(); + } + + @Override + public CommentHandler getCommentHandler() { + return new BladeCommentHandler(); + } + + @Override + public boolean hasHintsProvider() { + return true; + } + + @Override + public HintsProvider getHintsProvider() { + return new BladeHintsProvider(); + } + + @Override + public boolean hasFormatter() { + return true; + } + + @Override + public Formatter getFormatter() { + return new BladeFormatter(); + } + + @Override + public DeclarationFinder getDeclarationFinder() { + return new BladeDeclarationFinder(); + } + + @Override + @SuppressWarnings("rawtypes") + public SemanticAnalyzer getSemanticAnalyzer() { + return new BladeSemanticAnalyzer(); + } + + /** + * flag for detecting if we are in a string context enables to select the + * blade view "layout.index" string value on double click without + * interpreting the same thing for javascript objects + */ + public static volatile Boolean hasQuote = false; + + @Override + public boolean isIdentifierChar(char c) { + /** + * Includes things you'd want selected as a unit when double clicking in + * the editor + */ + //also used for completion items filtering! + if (c == '"' || c == '\'') { + hasQuote = true; + } + return Character.isJavaIdentifierPart(c) + || (c == '@') + || (hasQuote && c == '.') || (c == '_'); + } + + private static final Language language + = new BladeLanguageHierarchy() { + + @Override + protected String mimeType() { + return BladeLanguage.MIME_TYPE; + } + + @Override + protected Lexer createLexer(LexerRestartInfo info) { + return new BladeLexer(info); + } + + }.language(); + + @NbBundle.Messages("Source=&Source Blade") + @MultiViewElement.Registration( + displayName = "#Source", + persistenceType = TopComponent.PERSISTENCE_ONLY_OPENED, + mimeType = BladeLanguage.MIME_TYPE, + preferredID = "blade.source", + position = 100 + ) + public static MultiViewEditorElement createMultiViewEditorElement(Lookup context) { + return new MultiViewEditorElement(context); + } + +} diff --git a/php/php.blade/src/org/netbeans/modules/php/blade/editor/BladeSemanticAnalyzer.java b/php/php.blade/src/org/netbeans/modules/php/blade/editor/BladeSemanticAnalyzer.java new file mode 100644 index 000000000000..ac34e485cac6 --- /dev/null +++ b/php/php.blade/src/org/netbeans/modules/php/blade/editor/BladeSemanticAnalyzer.java @@ -0,0 +1,102 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.php.blade.editor; + +import java.util.EnumSet; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import org.netbeans.api.project.Project; +import org.netbeans.modules.csl.api.ColoringAttributes; +import org.netbeans.modules.csl.api.OffsetRange; +import org.netbeans.modules.csl.api.SemanticAnalyzer; +import org.netbeans.modules.parsing.spi.Scheduler; +import org.netbeans.modules.parsing.spi.SchedulerEvent; +import org.netbeans.modules.php.blade.editor.directives.CustomDirectives; +import org.netbeans.modules.php.blade.editor.parser.BladeParserResult; +import org.netbeans.modules.php.blade.project.ProjectUtils; +import org.openide.filesystems.FileObject; + +/** + * coloring configured in fonts and colors + * + * @author bhaidu + */ +public class BladeSemanticAnalyzer extends SemanticAnalyzer { + + private boolean cancelled; + public static final EnumSet UNDEFINED_FIELD_SET = EnumSet.of(ColoringAttributes.UNDEFINED); + public static final EnumSet CUSTOM_DIRECTIVE_SET = EnumSet.of(ColoringAttributes.DECLARATION); + private Map> semanticHighlights; + + @Override + public Map> getHighlights() { + return semanticHighlights; + } + + protected final synchronized boolean isCancelled() { + return cancelled; + } + + protected final synchronized void resume() { + cancelled = false; + } + + @Override + public void cancel() { + cancelled = true; + } + + @Override + public int getPriority() { + return 0; + } + + @Override + public Class getSchedulerClass() { + return Scheduler.EDITOR_SENSITIVE_TASK_SCHEDULER; + } + + @Override + public void run(BladeParserResult parserResult, SchedulerEvent event) { + resume(); + + if (isCancelled()) { + return; + } + + Map> highlights + = new HashMap<>(); + FileObject fo = parserResult.getFileObject(); + Project project = ProjectUtils.getMainOwner(fo); + CustomDirectives ct = CustomDirectives.getInstance(project); + + //highlight custom directives which are not found in the project configuration + for (Map.Entry entry : parserResult.getBladeCustomDirectiveOccurences().getAll().entrySet()) { + if (ct.customDirectiveConfigured(entry.getValue()) ) { + highlights.put(entry.getKey(), CUSTOM_DIRECTIVE_SET); + continue; + } + highlights.put(entry.getKey(), UNDEFINED_FIELD_SET); + } + + this.semanticHighlights = highlights; + } + +} diff --git a/php/php.blade/src/org/netbeans/modules/php/blade/editor/EditorStringUtils.java b/php/php.blade/src/org/netbeans/modules/php/blade/editor/EditorStringUtils.java new file mode 100644 index 000000000000..0ef7c48f8d1b --- /dev/null +++ b/php/php.blade/src/org/netbeans/modules/php/blade/editor/EditorStringUtils.java @@ -0,0 +1,53 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.php.blade.editor; + +/** + * + * @author bhaidu + */ +public final class EditorStringUtils { + + public static final String NAMESPACE_SEPARATOR = "\\"; // NOI18N + + private EditorStringUtils() { + } + + public static boolean isQuotedString(String text) { + if (text.length() < 2) { + return false; + } + return (text.startsWith("'") && text.endsWith("'")) // NOI18N + || (text.startsWith("\"") && text.endsWith("\"")); // NOI18N + } + + public static String stripSurroundingQuotes(String text) { + if (!isQuotedString(text)) { + return text; + } + return text.substring(1, text.length() - 1); + } + + public static String trimNamespace(String namespace) { + assert namespace.length() > 2; + int subOffset = namespace.startsWith(NAMESPACE_SEPARATOR) ? 1 : 0; + int endOffset = namespace.endsWith(NAMESPACE_SEPARATOR) ? 1 : 0; + return namespace.substring(subOffset, namespace.length() - endOffset); + } +} diff --git a/php/php.blade/src/org/netbeans/modules/php/blade/editor/EditorUtils.java b/php/php.blade/src/org/netbeans/modules/php/blade/editor/EditorUtils.java new file mode 100644 index 000000000000..820a99f603f9 --- /dev/null +++ b/php/php.blade/src/org/netbeans/modules/php/blade/editor/EditorUtils.java @@ -0,0 +1,55 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.php.blade.editor; + +import javax.swing.text.Document; +import org.netbeans.api.annotations.common.CheckForNull; +import org.netbeans.api.project.Project; +import org.netbeans.modules.editor.NbEditorUtilities; +import org.netbeans.modules.php.blade.project.ProjectUtils; +import org.openide.filesystems.FileObject; +import org.openide.loaders.DataObject; + +/** + * + * @author bogdan + */ +public final class EditorUtils { + + private EditorUtils() { + } + + @CheckForNull + public static FileObject getFileObject(Document doc) { + DataObject dObject = NbEditorUtilities.getDataObject(doc); + if (dObject != null) { + return dObject.getPrimaryFile().getParent(); + } + return null; + } + + @CheckForNull + public static Project getProjectOwner(Document doc) { + FileObject file = getFileObject(doc); + if (file == null) { + return null; + } + return ProjectUtils.getMainOwner(file); + } +} diff --git a/php/php.blade/src/org/netbeans/modules/php/blade/editor/FileSystemUtils.java b/php/php.blade/src/org/netbeans/modules/php/blade/editor/FileSystemUtils.java new file mode 100644 index 000000000000..239c3ba57228 --- /dev/null +++ b/php/php.blade/src/org/netbeans/modules/php/blade/editor/FileSystemUtils.java @@ -0,0 +1,53 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.php.blade.editor; + +import javax.swing.text.Document; +import org.netbeans.api.project.Project; +import org.netbeans.modules.editor.NbEditorUtilities; +import org.netbeans.modules.php.blade.project.ProjectUtils; +import org.openide.filesystems.FileObject; +import org.openide.loaders.DataObject; + +/** + * + * @author bogdan + */ +public final class FileSystemUtils { + + private FileSystemUtils() { + + } + + public static FileObject getFileObjectFromDoc(Document doc) { + DataObject dObject = NbEditorUtilities.getDataObject(doc); + if (dObject != null) { + return dObject.getPrimaryFile().getParent(); + } + return null; + } + + public static Project getProjectOwner(Document doc) { + FileObject file = getFileObjectFromDoc(doc); + if (file == null){ + return null; + } + return ProjectUtils.getMainOwner(file); + } +} diff --git a/php/php.blade/src/org/netbeans/modules/php/blade/editor/HyperlinkProviderImpl.java b/php/php.blade/src/org/netbeans/modules/php/blade/editor/HyperlinkProviderImpl.java new file mode 100644 index 000000000000..dba15fd7a01a --- /dev/null +++ b/php/php.blade/src/org/netbeans/modules/php/blade/editor/HyperlinkProviderImpl.java @@ -0,0 +1,205 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.php.blade.editor; + +import java.io.IOException; +import java.util.Arrays; +import java.util.EnumSet; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import javax.swing.text.Document; +import org.netbeans.api.editor.mimelookup.MimeRegistration; +import org.netbeans.lib.editor.hyperlink.spi.HyperlinkProviderExt; +import org.netbeans.lib.editor.hyperlink.spi.HyperlinkType; +import org.netbeans.api.editor.document.LineDocumentUtils; +import org.netbeans.api.lexer.Token; +import org.netbeans.api.lexer.TokenSequence; +import org.netbeans.api.project.Project; +import org.netbeans.editor.BaseDocument; +import static org.netbeans.lib.editor.hyperlink.spi.HyperlinkType.GO_TO_DECLARATION; +import org.netbeans.modules.php.api.util.FileUtils; +import org.netbeans.modules.php.blade.editor.lexer.BladeLexerUtils; +import org.netbeans.modules.php.blade.editor.path.BladePathUtils; +import org.netbeans.modules.php.blade.project.BladeProjectProperties; +import org.netbeans.modules.php.editor.lexer.PHPTokenId; +import org.openide.filesystems.FileObject; +import org.openide.loaders.DataObject; +import org.openide.text.Line; +import org.openide.text.NbDocument; +import org.openide.util.Exceptions; + +/** + * Similar to a declaration finder + * + * + * @author bhaidu + */ +@MimeRegistration(mimeType = FileUtils.PHP_MIME_TYPE, service = HyperlinkProviderExt.class) +public class HyperlinkProviderImpl implements HyperlinkProviderExt { + + private String methodName; + private String identifiableText; + private String tooltipText = ""; // NOI18N + private FileObject goToFile; + private int goToOffset = 0; + public static final int MIN_STRING_IDENTIIFER_LENGTH = 5; + public static final String FILE_TITLE = "Blade Template File"; // NOI18N + + private final String[] viewMethods = new String[]{"view", "render", "make"}; // NOI18N + private final Set viewMethodSet = new HashSet<>(Arrays.asList(viewMethods)); + + public enum DeclarationType { + VIEW_PATH; + } + + @Override + public Set getSupportedHyperlinkTypes() { + return EnumSet.of(HyperlinkType.GO_TO_DECLARATION, HyperlinkType.ALT_HYPERLINK); + } + + @Override + public boolean isHyperlinkPoint(Document doc, int offset, HyperlinkType type) { + if (!nonLaravelDeclFinderEnabled(doc)) { + return false; + } + return getHyperlinkSpan(doc, offset, type) != null; + } + + @Override + public int[] getHyperlinkSpan(Document doc, int offset, HyperlinkType type) { + if (!nonLaravelDeclFinderEnabled(doc)) { + return null; + } + if (!type.equals(HyperlinkType.GO_TO_DECLARATION)) { + //not handled by a LSP handler + return null; + } + + BaseDocument baseDoc = (BaseDocument) doc; + int lineStart = LineDocumentUtils.getLineStart(baseDoc, offset); + TokenSequence tokensq = BladeLexerUtils.getLockedPhpTokenSequence(doc, offset); + + if (tokensq == null) { + return null; + } + + Token currentToken = tokensq.token(); + int startOffset = tokensq.offset(); + + if (currentToken == null) { + return null; + } + + String focusedText = currentToken.text().toString(); + + //2 char config are not that relevant + if (focusedText.length() < MIN_STRING_IDENTIIFER_LENGTH || !EditorStringUtils.isQuotedString(focusedText)) { + return null; + } + + identifiableText = focusedText.substring(1, focusedText.length() - 1); + PHPTokenId prevTokenId = null; + + while (tokensq.movePrevious() && tokensq.offset() >= lineStart) { + Token token = tokensq.token(); + if (token == null) { + break; + } + String text = token.text().toString(); + PHPTokenId id = token.id(); + + if (prevTokenId != null && id.equals(PHPTokenId.PHP_STRING)) { + methodName = text; + //tooltip text + + if (viewMethodSet.contains(methodName)) { + FileObject currentFile = FileSystemUtils.getFileObjectFromDoc(doc); + + if (currentFile == null) { + return null; + } + List includedFiles = BladePathUtils.findFileObjectsForBladeViewPath(currentFile, identifiableText); + String viewPath = BladePathUtils.toBladeToProjectFilePath(identifiableText); + + for (FileObject includedFile : includedFiles) { + goToFile = includedFile; + tooltipText = FILE_TITLE + " File : " + viewPath // NOI18N + + "

" + identifiableText + ""; // NOI18N + goToOffset = 0; + break; + } + + return new int[]{startOffset, startOffset + currentToken.length()}; + } + + } + + if (id.equals(PHPTokenId.PHP_TOKEN) && text.equals("(")) { // NOI18N + prevTokenId = id; + } + } + return null; + } + + @Override + public void performClickAction(Document doc, int offset, HyperlinkType type) { + if (!type.equals(GO_TO_DECLARATION)) { + return; + } + if (viewMethodSet.contains(methodName)) { + if (goToFile != null) { + openDocument(goToFile, goToOffset); + } + } + } + + @Override + public String getTooltipText(Document doc, int offset, HyperlinkType type) { + return "" + tooltipText + ""; // NOI18N + } + + private void openDocument(FileObject f, int offset) { + try { + DataObject dob = DataObject.find(f); + NbDocument.openDocument(dob, offset, Line.ShowOpenType.OPEN, Line.ShowVisibilityType.FOCUS); + + } catch (IOException ex) { + Exceptions.printStackTrace(ex); + } + } + + /** + * compatible relation with laravel framework plugin + * blade editor is not a Php Extension, so that's the reason for the implementation of HyperlinkProvider. + * + * @param doc + * @return + */ + private boolean nonLaravelDeclFinderEnabled(Document doc) { + Project projectOwner = FileSystemUtils.getProjectOwner(doc); + if (projectOwner == null) { + return false; + } + BladeProjectProperties bladeProperties = BladeProjectProperties.getInstance(projectOwner); + + return bladeProperties.getNonLaravelDeclFinderFlag(); + } + +} diff --git a/php/php.blade/src/org/netbeans/modules/php/blade/editor/ResourceUtilities.java b/php/php.blade/src/org/netbeans/modules/php/blade/editor/ResourceUtilities.java new file mode 100644 index 000000000000..54fc509913a6 --- /dev/null +++ b/php/php.blade/src/org/netbeans/modules/php/blade/editor/ResourceUtilities.java @@ -0,0 +1,55 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.php.blade.editor; + +import javax.swing.ImageIcon; +import org.netbeans.api.annotations.common.CheckForNull; +import org.openide.util.ImageUtilities; + +/** + * + * @author bhaidu + */ +public final class ResourceUtilities { + + public static final String FOLDER = "org/openide/loaders/defaultFolder.gif";//NOI18N + public static final String ICON_BASE = "org/netbeans/modules/php/blade/resources/"; //NOI18N + public static final String DIRECTIVE_ICON = ICON_BASE + "icons/at.png"; //NOI18N + public static final String BLADE_VIEW = ICON_BASE + "icons/blade_file.png"; //NOI18N + public static final String LAYOUT_IDENTIFIER = ICON_BASE + "icons/layout.png"; //NOI18N + public static final String COMPONENT_TAG = "org/netbeans/modules/html/custom/resources/custom_html_element.png"; //NOI18N + public static final String CSS_FILE = "org/netbeans/modules/css/visual/resources/style_sheet_16.png"; //NOI18N + public static final String JS_FILE = "org/netbeans/modules/css/visual/resources/javascript.png"; //NOI18N + public static final String CUSTOM_HTML_ICON = "org/netbeans/modules/html/custom/resources/custom_html_element.png"; //NOI18N + public static final String XML_ATTRIBUTE_ICON = "org/netbeans/modules/xml/schema/completion/resources/attribute.png"; //NOI18N + + private ResourceUtilities() { + + } + + @CheckForNull + public static ImageIcon loadResourceIcon(String path) { + return ImageUtilities.loadImageIcon(ICON_BASE + path, false); + } + + @CheckForNull + public static ImageIcon loadLayoutIcon() { + return ImageUtilities.loadImageIcon(LAYOUT_IDENTIFIER, false); + } +} diff --git a/php/php.blade/src/org/netbeans/modules/php/blade/editor/actions/AntlrDebugAction.java b/php/php.blade/src/org/netbeans/modules/php/blade/editor/actions/AntlrDebugAction.java new file mode 100644 index 000000000000..55e012d7c437 --- /dev/null +++ b/php/php.blade/src/org/netbeans/modules/php/blade/editor/actions/AntlrDebugAction.java @@ -0,0 +1,199 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.php.blade.editor.actions; + +import java.awt.event.ActionEvent; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import javax.swing.AbstractAction; +import javax.swing.Action; +import javax.swing.JComponent; +import javax.swing.JMenu; +import javax.swing.JMenuItem; +import javax.swing.JPopupMenu; +import javax.swing.event.PopupMenuEvent; +import javax.swing.event.PopupMenuListener; +import org.openide.awt.ActionID; +import org.openide.awt.ActionRegistration; +import org.openide.awt.Actions; +import org.openide.awt.DynamicMenuContent; +import org.openide.nodes.Node; +import org.openide.util.ContextAwareAction; +import org.openide.util.HelpCtx; +import org.openide.util.Lookup; +import org.openide.util.actions.Presenter; + +/** + * + * @author bhaidu + */ +@ActionID(id = "org.netbeans.modules.php.blade.editor.actions.AntlrDebug", category = "System") +@ActionRegistration(displayName = "Antlr Debug", lazy=false) +public class AntlrDebugAction extends AbstractAction implements ContextAwareAction, Presenter.Popup { + + private Node node; + + public void setNode(Node node){ + this.node = node; + } + + @Override + public Action createContextAwareInstance(Lookup lkp) { + Node folderNode = lkp.lookup(Node.class); + AntlrDebugAction act = new AntlrDebugAction(); + act.setNode(folderNode); + return act; + } + + static final long serialVersionUID = 4906417339959070129L; + + @Override + public void actionPerformed(ActionEvent ae) { + assert false; + } + + @Override + public JMenuItem getPopupPresenter() { + return new AntlrDebugAction.Popup(this); + } + + private List generate(Action gdlAction, boolean forMenu) { + List actions = getAntlrDebugActions(); + List list = new ArrayList<>(actions.size()); + + Lookup lookup; + + if (gdlAction instanceof Lookup.Provider) { + lookup = ((Lookup.Provider) gdlAction).getLookup(); + } else { + lookup = null; + } + + for (Action a : actions) { + + // Retrieve context sensitive action instance if possible. + if (lookup != null && a instanceof ContextAwareAction) { + a = ((ContextAwareAction) a).createContextAwareInstance(lookup); + } + + if (a != null && a.isEnabled()) { + JMenuItem mi; + mi = new JMenuItem(); + Actions.connect(mi, a, !forMenu); + list.add(mi); + } + } + + return list; + } + + List getAntlrDebugActions() { + + List arr = new ArrayList<>(); + if (node != null) { + arr.add(new ViewAntlrLexerTokensAction(node)); + arr.add(new ViewAntlrColoringTokensAction(node)); + arr.add(new ViewAntlrFormatterTokensAction(node)); + } +// List actions = Utilities.actionsForPath("Actions/AntlrDebugActions"); +// arr.addAll(actions); + //add the actions + return arr; + } + + private final class Popup extends JMenuItem implements DynamicMenuContent { + + private final JMenu menu = new MyMenu(); + private JPopupMenu lastPopup = null; + /** + * Associated tools action. + */ + private final Action antlrDebugAction; + + public Popup(Action antlrDebugAction) { + super(); + this.antlrDebugAction = antlrDebugAction; + HelpCtx.setHelpIDString(menu, AntlrDebugAction.class.getName()); + + } + + @Override + public JComponent[] getMenuPresenters() { + return synchMenuPresenters(new JComponent[0]); + } + + @Override + public JComponent[] synchMenuPresenters(JComponent[] jcs) { + return new JMenuItem[]{menu}; + } + + private class MyMenu extends org.openide.awt.JMenuPlus implements PopupMenuListener { + + MyMenu() { + super("Antlr Debug"); + } + + @Override + public JPopupMenu getPopupMenu() { + JPopupMenu popup = super.getPopupMenu(); + fillSubmenu(popup); + + return popup; + } + + private void fillSubmenu(JPopupMenu pop) { + if (lastPopup != null) { + return; + } + pop.addPopupMenuListener(this); + lastPopup = pop; + + removeAll(); + Iterator it = generate(antlrDebugAction, false).iterator(); + + while (it.hasNext()) { + java.awt.Component item = (java.awt.Component) it.next(); + + if (item == null) { + addSeparator(); + } else { + add(item); + } + } + } + + @Override + public void popupMenuWillBecomeVisible(PopupMenuEvent e) { + + } + + @Override + public void popupMenuWillBecomeInvisible(PopupMenuEvent e) { + + } + + @Override + public void popupMenuCanceled(PopupMenuEvent e) { + + } + } + } + +} diff --git a/php/php.blade/src/org/netbeans/modules/php/blade/editor/actions/FindUsage.java b/php/php.blade/src/org/netbeans/modules/php/blade/editor/actions/FindUsage.java new file mode 100644 index 000000000000..c48a5d25c61b --- /dev/null +++ b/php/php.blade/src/org/netbeans/modules/php/blade/editor/actions/FindUsage.java @@ -0,0 +1,60 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.php.blade.editor.actions; + +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import javax.swing.AbstractAction; +import org.netbeans.modules.php.blade.editor.path.BladePathUtils; +import org.netbeans.modules.php.blade.editor.refactoring.BladePathInfo; +import org.netbeans.modules.php.blade.editor.refactoring.WhereBladePathUsedRefactoringUIImpl; +import org.netbeans.modules.refactoring.spi.ui.UI; +import org.openide.awt.ActionID; +import org.openide.awt.ActionRegistration; +import org.openide.filesystems.FileObject; +import org.openide.nodes.Node; +import org.openide.windows.TopComponent; + +/** + * + * @author bogdan + */ +@ActionID(id = "org.netbeans.modules.php.blade.editor.actions.FindUsage", category = "TemplateActions") +@ActionRegistration(displayName = "Template Usages") +public class FindUsage extends AbstractAction implements ActionListener { + + private final Node node; + + public FindUsage(Node node) { + this.node = node; + } + + @Override + public void actionPerformed(ActionEvent e) { + FileObject fo = node.getLookup().lookup(FileObject.class); + String bladePath = BladePathUtils.toBladeViewPath(fo); + if (bladePath == null) { + return; + } + BladePathInfo si = new BladePathInfo(fo, bladePath); + UI.openRefactoringUI(new WhereBladePathUsedRefactoringUIImpl(si), + TopComponent.getRegistry().getActivated()); + } + +} diff --git a/php/php.blade/src/org/netbeans/modules/php/blade/editor/actions/ViewAntlrColoringTokensAction.java b/php/php.blade/src/org/netbeans/modules/php/blade/editor/actions/ViewAntlrColoringTokensAction.java new file mode 100644 index 000000000000..12c63d243d98 --- /dev/null +++ b/php/php.blade/src/org/netbeans/modules/php/blade/editor/actions/ViewAntlrColoringTokensAction.java @@ -0,0 +1,141 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.php.blade.editor.actions; + +import java.awt.BorderLayout; +import java.awt.Rectangle; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.io.IOException; +import java.io.Reader; +import java.io.StringReader; +import javax.swing.AbstractAction; +import javax.swing.JEditorPane; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.text.BadLocationException; +import javax.swing.text.Document; +import javax.swing.text.EditorKit; +import org.antlr.v4.runtime.CharStream; +import org.antlr.v4.runtime.CharStreams; +import org.antlr.v4.runtime.CommonTokenStream; +import org.antlr.v4.runtime.Token; +import org.antlr.v4.runtime.Vocabulary; +import org.netbeans.modules.php.blade.syntax.StringUtils; +import org.netbeans.modules.php.blade.syntax.antlr4.v10.BladeAntlrColoringLexer; +import org.openide.awt.ActionID; +import org.openide.awt.ActionRegistration; +import org.openide.filesystems.FileObject; +import org.openide.nodes.Node; +import org.openide.util.Exceptions; +import org.openide.windows.TopComponent; + +/** + * + * @author bhaidu + */ +@ActionID(id = "org.netbeans.modules.php.blade.editor.actions.ViewAntlrColoringTokens", category = "DebugAntlrActions") +@ActionRegistration(displayName = "AntlrColoring Tokens") +public class ViewAntlrColoringTokensAction extends AbstractAction implements ActionListener { + + private final Node node; + private transient JEditorPane viewer; + + public ViewAntlrColoringTokensAction(Node node) { + this.node = node; + putValue(NAME, "AntlrColoring Tokens"); // NOI18N + } + + @Override + public void actionPerformed(ActionEvent e) { + FileObject fo = node.getLookup().lookup(FileObject.class); + if (fo == null) { + return; + } + AntlrLexerPreviewComponent comp = new AntlrLexerPreviewComponent(fo); + comp.open(); + comp.setVisible(true); + } + + public final class AntlrLexerPreviewComponent extends TopComponent { + + private final FileObject fileObject; + + public AntlrLexerPreviewComponent(FileObject fo) { + this.fileObject = fo; + initComponents(); + } + + @Override + public int getPersistenceType() { + return TopComponent.PERSISTENCE_NEVER; + } + + protected void initComponents() { + setName("Antlr coloring token preview - " + fileObject.getName()); // NOI18N + setLayout(new BorderLayout()); + viewer = new JEditorPane(); + viewer.setContentType("text/plain"); // NOI18N + viewer.setEditable(false); + //viewer.addHyperlinkListener(this::linkHandler); + + JPanel panel = new JPanel(new BorderLayout()); + panel.add(new JScrollPane(viewer), BorderLayout.CENTER); + add(panel); + Rectangle vis = viewer.getVisibleRect(); + try { + CharStream cs = CharStreams.fromString(String.valueOf(fileObject.asText())); + BladeAntlrColoringLexer lexer = new BladeAntlrColoringLexer(cs); + Vocabulary vocabulary = lexer.getVocabulary(); + CommonTokenStream tokens = new CommonTokenStream(lexer); + tokens.fill(); + Document doc = viewer.getDocument(); + + // Would be better to create some diff and update the changed elemets + doc.remove(0, doc.getLength()); + StringBuilder result = new StringBuilder(); + + for (Token token : tokens.getTokens()) { + int tokenId = token.getType(); + String text = token.getText(); + result.append("Token #"); // NOI18N + result.append(tokenId); + result.append(" "); // NOI18N + result.append(vocabulary.getDisplayName(tokenId)); + String tokenText = StringUtils.replaceLinesAndTabs(text); + if (!tokenText.isEmpty()) { + result.append(" "); // NOI18N + result.append("["); // NOI18N + result.append(token); + result.append("]"); // NOI18N + } + result.append("\n"); // NOI18N + } + + EditorKit kit = viewer.getEditorKit(); + Reader reader = new StringReader(result.toString()); + //doc. + kit.read(reader, doc, 0); + viewer.scrollRectToVisible(vis); + } catch (IOException | BadLocationException ex) { + Exceptions.printStackTrace(ex); + } + } + } +} diff --git a/php/php.blade/src/org/netbeans/modules/php/blade/editor/actions/ViewAntlrFormatterTokensAction.java b/php/php.blade/src/org/netbeans/modules/php/blade/editor/actions/ViewAntlrFormatterTokensAction.java new file mode 100644 index 000000000000..7d7850e2b9a5 --- /dev/null +++ b/php/php.blade/src/org/netbeans/modules/php/blade/editor/actions/ViewAntlrFormatterTokensAction.java @@ -0,0 +1,140 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.php.blade.editor.actions; + +import java.awt.BorderLayout; +import java.awt.Rectangle; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.io.IOException; +import java.io.Reader; +import java.io.StringReader; +import javax.swing.AbstractAction; +import javax.swing.JEditorPane; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.text.BadLocationException; +import javax.swing.text.Document; +import javax.swing.text.EditorKit; +import org.antlr.v4.runtime.CharStream; +import org.antlr.v4.runtime.CharStreams; +import org.antlr.v4.runtime.CommonTokenStream; +import org.antlr.v4.runtime.Token; +import org.antlr.v4.runtime.Vocabulary; +import org.netbeans.modules.php.blade.syntax.StringUtils; +import org.netbeans.modules.php.blade.syntax.antlr4.formatter.BladeAntlrFormatterLexer; +import org.openide.awt.ActionID; +import org.openide.awt.ActionRegistration; +import org.openide.filesystems.FileObject; +import org.openide.nodes.Node; +import org.openide.util.Exceptions; +import org.openide.windows.TopComponent; + +/** + * + * @author bhaidu + */ +@ActionID(id = "org.netbeans.modules.php.blade.editor.actions.ViewAntlrFormatterTokens", category = "DebugAntlrActions") +@ActionRegistration(displayName = "AntlrFormatter Tokens") +public class ViewAntlrFormatterTokensAction extends AbstractAction implements ActionListener { + + private final Node node; + private transient JEditorPane viewer; + + public ViewAntlrFormatterTokensAction(Node node) { + this.node = node; + putValue(NAME, "AntlrFormatter Tokens"); // NOI18N + } + + @Override + public void actionPerformed(ActionEvent e) { + FileObject fo = node.getLookup().lookup(FileObject.class); + if (fo == null) { + return; + } + AntlrLexerPreviewComponent comp = new AntlrLexerPreviewComponent(fo); + comp.open(); + comp.setVisible(true); + } + + public final class AntlrLexerPreviewComponent extends TopComponent { + + private final FileObject fileObject; + + public AntlrLexerPreviewComponent(FileObject fo) { + this.fileObject = fo; + initComponents(); + } + + @Override + public int getPersistenceType() { + return TopComponent.PERSISTENCE_NEVER; + } + + protected void initComponents() { + setName("Antlr formatter token preview - " + fileObject.getName()); // NOI18N + setLayout(new BorderLayout()); + viewer = new JEditorPane(); + viewer.setContentType("text/plain"); // NOI18N + viewer.setEditable(false); + + JPanel panel = new JPanel(new BorderLayout()); + panel.add(new JScrollPane(viewer), BorderLayout.CENTER); + add(panel); + Rectangle vis = viewer.getVisibleRect(); + try { + CharStream cs = CharStreams.fromString(String.valueOf(fileObject.asText())); + BladeAntlrFormatterLexer lexer = new BladeAntlrFormatterLexer(cs); + Vocabulary vocabulary = lexer.getVocabulary(); + CommonTokenStream tokens = new CommonTokenStream(lexer); + tokens.fill(); + Document doc = viewer.getDocument(); + + // Would be better to create some diff and update the changed elemets + doc.remove(0, doc.getLength()); + StringBuilder result = new StringBuilder(); + + for (Token token : tokens.getTokens()) { + int tokenId = token.getType(); + String text = token.getText(); + result.append("Token #"); // NOI18N + result.append(tokenId); + result.append(" "); // NOI18N + result.append(vocabulary.getDisplayName(tokenId)); + String tokenText = StringUtils.replaceLinesAndTabs(text); + if (!tokenText.isEmpty()) { + result.append(" "); // NOI18N + result.append("["); // NOI18N + result.append(token); + result.append("]"); // NOI18N + } + result.append("\n"); // NOI18N + } + + EditorKit kit = viewer.getEditorKit(); + Reader reader = new StringReader(result.toString()); + //doc. + kit.read(reader, doc, 0); + viewer.scrollRectToVisible(vis); + } catch (IOException | BadLocationException ex) { + Exceptions.printStackTrace(ex); + } + } + } +} diff --git a/php/php.blade/src/org/netbeans/modules/php/blade/editor/actions/ViewAntlrLexerTokensAction.java b/php/php.blade/src/org/netbeans/modules/php/blade/editor/actions/ViewAntlrLexerTokensAction.java new file mode 100644 index 000000000000..cc9bac96909f --- /dev/null +++ b/php/php.blade/src/org/netbeans/modules/php/blade/editor/actions/ViewAntlrLexerTokensAction.java @@ -0,0 +1,140 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.php.blade.editor.actions; + +import java.awt.BorderLayout; +import java.awt.Rectangle; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.io.IOException; +import java.io.Reader; +import java.io.StringReader; +import javax.swing.AbstractAction; +import javax.swing.JEditorPane; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.text.BadLocationException; +import javax.swing.text.Document; +import javax.swing.text.EditorKit; +import org.antlr.v4.runtime.CharStream; +import org.antlr.v4.runtime.CharStreams; +import org.antlr.v4.runtime.CommonTokenStream; +import org.antlr.v4.runtime.Token; +import org.antlr.v4.runtime.Vocabulary; +import org.netbeans.modules.php.blade.syntax.StringUtils; +import org.netbeans.modules.php.blade.syntax.antlr4.v10.BladeAntlrLexer; +import org.openide.awt.ActionID; +import org.openide.awt.ActionRegistration; +import org.openide.filesystems.FileObject; +import org.openide.nodes.Node; +import org.openide.util.Exceptions; +import org.openide.windows.TopComponent; + +/** + * + * @author bhaidu + */ +@ActionID(id = "org.netbeans.modules.php.blade.editor.actions.ViewAntlrLexerTokens", category = "DebugAntlrActions") +@ActionRegistration(displayName = "AntlrLexer Tokens") +public class ViewAntlrLexerTokensAction extends AbstractAction implements ActionListener { + + private final Node node; + private transient JEditorPane viewer; + + public ViewAntlrLexerTokensAction(Node node) { + this.node = node; + putValue(NAME, "AntlrLexer Tokens"); // NOI18N + } + + @Override + public void actionPerformed(ActionEvent e) { + FileObject fo = node.getLookup().lookup(FileObject.class); + if (fo == null) { + return; + } + AntlrLexerPreviewComponent comp = new AntlrLexerPreviewComponent(fo); + comp.open(); + comp.setVisible(true); + } + + public final class AntlrLexerPreviewComponent extends TopComponent { + + private final FileObject fileObject; + + public AntlrLexerPreviewComponent(FileObject fo) { + this.fileObject = fo; + initComponents(); + } + + @Override + public int getPersistenceType() { + return TopComponent.PERSISTENCE_NEVER; + } + + protected void initComponents() { + setName("Antlr token preview - " + fileObject.getName()); // NOI18N + setLayout(new BorderLayout()); + viewer = new JEditorPane(); + viewer.setContentType("text/plain"); // NOI18N + viewer.setEditable(false); + + JPanel panel = new JPanel(new BorderLayout()); + panel.add(new JScrollPane(viewer), BorderLayout.CENTER); + add(panel); + Rectangle vis = viewer.getVisibleRect(); + try { + CharStream cs = CharStreams.fromString(String.valueOf(fileObject.asText())); + BladeAntlrLexer lexer = new BladeAntlrLexer(cs); + Vocabulary vocabulary = lexer.getVocabulary(); + CommonTokenStream tokens = new CommonTokenStream(lexer); + tokens.fill(); + Document doc = viewer.getDocument(); + + // Would be better to create some diff and update the changed elemets + doc.remove(0, doc.getLength()); + StringBuilder result = new StringBuilder(); + + for (Token token : tokens.getTokens()) { + int tokenId = token.getType(); + String text = token.getText(); + result.append("Token #"); // NOI18N + result.append(tokenId); + result.append(" "); // NOI18N + result.append(vocabulary.getDisplayName(tokenId)); + String tokenText = StringUtils.replaceLinesAndTabs(text); + if (!tokenText.isEmpty()) { + result.append(" "); // NOI18N + result.append("["); // NOI18N + result.append(token); + result.append("]"); // NOI18N + } + result.append("\n"); // NOI18N + } + + EditorKit kit = viewer.getEditorKit(); + Reader reader = new StringReader(result.toString()); + //doc. + kit.read(reader, doc, 0); + viewer.scrollRectToVisible(vis); + } catch (IOException | BadLocationException ex) { + Exceptions.printStackTrace(ex); + } + } + } +} diff --git a/php/php.blade/src/org/netbeans/modules/php/blade/editor/braces/BladeBracesMatcher.java b/php/php.blade/src/org/netbeans/modules/php/blade/editor/braces/BladeBracesMatcher.java new file mode 100644 index 000000000000..fc503a794b78 --- /dev/null +++ b/php/php.blade/src/org/netbeans/modules/php/blade/editor/braces/BladeBracesMatcher.java @@ -0,0 +1,308 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.php.blade.editor.braces; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import javax.swing.text.BadLocationException; +import org.antlr.v4.runtime.Token; +import org.netbeans.api.editor.mimelookup.MimeRegistration; +import org.netbeans.api.lexer.TokenHierarchy; +import org.netbeans.api.project.Project; +import org.netbeans.editor.BaseDocument; +import org.netbeans.modules.php.blade.editor.BladeLanguage; +import org.netbeans.modules.php.blade.editor.FileSystemUtils; +import org.netbeans.modules.php.blade.editor.directives.CustomDirectives; +import org.netbeans.modules.php.blade.editor.directives.CustomDirectives.CustomDirective; +import org.netbeans.modules.php.blade.editor.lexer.BladeLexerUtils; +import org.netbeans.modules.php.blade.editor.lexer.BladeTokenId; +import org.netbeans.modules.php.blade.syntax.BladeDirectivesUtils; +import static org.netbeans.modules.php.blade.syntax.BladeDirectivesUtils.END_DIRECTIVE_PREFIX; +import org.netbeans.modules.php.blade.syntax.BladeTagsUtils; +import org.netbeans.modules.php.blade.syntax.antlr4.utils.BaseBladeAntlrUtils; +import static org.netbeans.modules.php.blade.syntax.BladeTagsUtils.*; +import org.netbeans.modules.php.blade.syntax.antlr4.utils.BladeAntlrLexerUtils; +import org.netbeans.spi.editor.bracesmatching.BracesMatcher; +import org.netbeans.spi.editor.bracesmatching.BracesMatcherFactory; +import org.netbeans.spi.editor.bracesmatching.MatcherContext; +import org.netbeans.spi.lexer.antlr4.AntlrTokenSequence; + +/** + * brace matcher - block directives : @if @endif .. - output echo statements {{ + * }} {!! !!} + * + * @author bogdan + */ +public class BladeBracesMatcher implements BracesMatcher { + + public enum BraceDirectionType { + BLOCK_DIRECTIVE_END_TO_START, BLOCK_DIRECTIVE_START_TO_END, + CUSTOM_START_TO_END, CUSTOM_END_TO_START, + CURLY_END_TO_START, CURLY_START_TO_END, STOP + } + private final MatcherContext context; + private String tokenText; + private int tokenOffset; + private BraceDirectionType currentDirection; + + private BladeBracesMatcher(MatcherContext context) { + this.context = context; + } + + @Override + public int[] findOrigin() throws InterruptedException, BadLocationException { + int[] result = null; + tokenText = null; + tokenOffset = context.getSearchOffset(); + BaseDocument document = (BaseDocument) context.getDocument(); + document.readLock(); + try { + TokenHierarchy th = TokenHierarchy.get(document); + org.netbeans.api.lexer.Token token = BladeLexerUtils.getBladeToken(th, context.getSearchOffset()); + + if (token == null) { + return result; + } + + BladeTokenId id = token.id(); + tokenOffset = token.offset(th); + + switch (id) { + case BLADE_DIRECTIVE -> { + tokenText = token.text().toString().trim(); + currentDirection = findDirectiveBlockDirectionType(tokenText); + if (currentDirection.equals(BraceDirectionType.STOP)) { + return result; + } + return new int[]{tokenOffset, tokenOffset + token.length()}; + } + case BLADE_ECHO_DELIMITOR -> { + tokenText = token.text().toString().trim(); + currentDirection = isStartTag(tokenText) ? BraceDirectionType.CURLY_START_TO_END : BraceDirectionType.CURLY_END_TO_START; + return new int[]{tokenOffset, tokenOffset + token.length()}; + } + case BLADE_CUSTOM_DIRECTIVE -> { + tokenText = token.text().toString().trim(); + Project projectOwner = FileSystemUtils.getProjectOwner(document); + if (projectOwner == null) { + return result; + } + CustomDirectives customDirectives = CustomDirectives.getInstance(projectOwner); + if (customDirectives == null) { + return result; + } + for (List directiveCollection : customDirectives.getCustomDirectives().values()) { + for (CustomDirective customDirective : directiveCollection) { + if (customDirective.isBlockDirective() + && tokenText.equals(customDirective.getName())) { + currentDirection = BraceDirectionType.CUSTOM_START_TO_END; + return new int[]{tokenOffset, tokenOffset + token.length()}; + } + } + } + return result; + } + } + } finally { + document.readUnlock(); + } + return result; + } + + @Override + public int[] findMatches() throws InterruptedException, BadLocationException { + int[] result = null; + + if (tokenText == null) { + return result; + } + + return switch (currentDirection) { + case BLOCK_DIRECTIVE_START_TO_END -> findBlockDirectiveEnd(tokenText); + case BLOCK_DIRECTIVE_END_TO_START -> findBlockDirectiveStart(tokenText); + case CUSTOM_START_TO_END -> findCustomDirectiveEnd(tokenText); + case CUSTOM_END_TO_START -> findCustomDirectiveStart(tokenText); + case CURLY_START_TO_END -> findCloseTag(); + case CURLY_END_TO_START -> findOpenTag(); + default -> result; + }; + } + + private int[] findBlockDirectiveEnd(String directive) { + String[] endings = BladeDirectivesUtils.blockDirectiveEndings(directive); + + if (endings == null) { + return null; + } + + Set openingDirectives = BladeDirectivesUtils.blockDirectiveOpeningsSet(endings); + Set endDirectives = new HashSet<>(Arrays.asList(endings)); + + int searchOffset = tokenOffset + tokenText.length() + 1; + AntlrTokenSequence ats = BladeAntlrLexerUtils.getTokens(context.getDocument()); + + Token endToken = BaseBladeAntlrUtils.findForward(ats, + searchOffset, + endDirectives, + openingDirectives); + + return outputRange(endToken); + } + + private int[] findBlockDirectiveStart(String directive) { + String[] openings = BladeDirectivesUtils.blockDirectiveOpenings(directive); + + if (openings == null) { + if (!directive.startsWith(END_DIRECTIVE_PREFIX)) { + return null; + } + Project projectOwner = FileSystemUtils.getProjectOwner(context.getDocument()); + if (projectOwner == null) { + return null; + } + CustomDirectives customDirectives = CustomDirectives.getInstance(projectOwner); + if (customDirectives == null) { + return null; + } + String startTag = BladeDirectivesUtils.AT + directive.substring(END_DIRECTIVE_PREFIX.length()); // NOI18N + for (List directiveCollection : customDirectives.getCustomDirectives().values()) { + for (CustomDirective customDirective : directiveCollection) { + if (customDirective.isBlockDirective() + && startTag.equals(customDirective.getName())) { + openings = new String[]{startTag}; + break; + } + } + } + if (openings == null) { + return null; + } + } + + Set endDirectivesForBalance = BladeDirectivesUtils.blockDirectiveEndingsSet(openings); + Set openDirectives = new HashSet<>(Arrays.asList(openings)); + + int searchOffset = tokenOffset - 1; + AntlrTokenSequence ats = BladeAntlrLexerUtils.getTokens(context.getDocument()); + + Token startToken = BaseBladeAntlrUtils.findBackward(ats, + searchOffset, + openDirectives, + endDirectivesForBalance); + + return outputRange(startToken); + } + + public int[] findOpenTag() { + AntlrTokenSequence ats = BladeAntlrLexerUtils.getTokens(context.getDocument()); + String matchText = tokenText.equals(CONTENT_TAG_CLOSE) ? CONTENT_TAG_OPEN : RAW_TAG_OPEN; + int searchOffset = tokenOffset + tokenText.length() + 1; + Token startToken = BladeAntlrLexerUtils.findBackward(ats, + searchOffset, + new HashSet<>(Arrays.asList(matchText)), + new HashSet<>()); + + return outputRange(startToken); + } + + public int[] findCloseTag() { + AntlrTokenSequence ats = BladeAntlrLexerUtils.getTokens(context.getDocument()); + String matchText = tokenText.equals(CONTENT_TAG_OPEN) ? CONTENT_TAG_CLOSE : RAW_TAG_CLOSE; + int searchOffset = tokenOffset - 1; + Token endToken = BladeAntlrLexerUtils.findForward(ats, + searchOffset, + new HashSet<>(Arrays.asList(matchText)), + new HashSet<>()); + + return outputRange(endToken); + } + + public int[] findCustomDirectiveEnd(String directive) { + String[] pair = new String[]{BladeDirectivesUtils.END_DIRECTIVE_PREFIX + directive.substring(1)}; + Set stopDirectives = new HashSet<>(Arrays.asList(pair)); + Set startDirectiveForBalance = new HashSet<>(); + startDirectiveForBalance.add(directive); + + AntlrTokenSequence ats = BladeAntlrLexerUtils.getTokens(context.getDocument()); + int searchOffset = tokenOffset - 1; + Token endToken = BaseBladeAntlrUtils.findForward(ats, + searchOffset, + stopDirectives, + startDirectiveForBalance); + + return outputRange(endToken); + } + + public int[] findCustomDirectiveStart(String directive) { + int endTextLength = BladeDirectivesUtils.END_DIRECTIVE_PREFIX.length(); + String[] pair = new String[]{directive.substring(endTextLength)}; + Set stopDirectives = new HashSet<>(Arrays.asList(pair)); + Set startDirectiveForBalance = new HashSet<>(); + startDirectiveForBalance.add(directive); + + AntlrTokenSequence ats = BladeAntlrLexerUtils.getTokens(context.getDocument()); + int searchOffset = tokenOffset + tokenText.length() + 1; + Token endToken = BaseBladeAntlrUtils.findBackward(ats, + searchOffset, + stopDirectives, + startDirectiveForBalance); + + return outputRange(endToken); + } + + private boolean isStartTag(String tag) { + return Arrays.asList(BladeTagsUtils.outputStartTags()).indexOf(tag) >= 0; + } + + private BraceDirectionType findDirectiveBlockDirectionType(String tokenText) { + if (tokenText.startsWith(BladeDirectivesUtils.END_DIRECTIVE_PREFIX) + || tokenText.equals(BladeDirectivesUtils.DIRECTIVE_SHOW) + || tokenText.equals(BladeDirectivesUtils.DIRECTIVE_ELSEIF) + || tokenText.equals(BladeDirectivesUtils.DIRECTIVE_ELSE)) { + return BraceDirectionType.BLOCK_DIRECTIVE_END_TO_START; + } else if (BladeDirectivesUtils.blockDirectiveEndings(tokenText) != null) { + return BraceDirectionType.BLOCK_DIRECTIVE_START_TO_END; + } + + return BraceDirectionType.STOP; + } + + private int[] outputRange(Token token) { + if (token != null) { + String rangeTokenText = token.getText().trim(); + int start = token.getStartIndex(); + int end = start + rangeTokenText.length(); + return new int[]{start, end}; + } + + return null; + } + + @MimeRegistration(service = BracesMatcherFactory.class, mimeType = BladeLanguage.MIME_TYPE, position = 0) + public static final class Factory implements BracesMatcherFactory { + + @Override + public BracesMatcher createMatcher(MatcherContext context) { + return new BladeBracesMatcher(context); + } + + } +} diff --git a/php/php.blade/src/org/netbeans/modules/php/blade/editor/cache/QueryCache.java b/php/php.blade/src/org/netbeans/modules/php/blade/editor/cache/QueryCache.java new file mode 100644 index 000000000000..5cabba310317 --- /dev/null +++ b/php/php.blade/src/org/netbeans/modules/php/blade/editor/cache/QueryCache.java @@ -0,0 +1,109 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.php.blade.editor.cache; + +import java.time.LocalDateTime; +import java.time.temporal.ChronoUnit; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * + * @author bogdan + */ +public final class QueryCache { + + public static final Long DEFAULT_CACHE_TIMEOUT = 60000L; + + private Map> cacheMap; + private Long cacheTimeout; + + public QueryCache() { + this(DEFAULT_CACHE_TIMEOUT); + } + + public QueryCache(Long cacheTimeout) { + this.cacheTimeout = cacheTimeout; + this.clear(); + } + + public void clean() { + for (K key : this.getExpiredKeys()) { + this.remove(key); + } + } + + public boolean containsKey(K key) { + return this.cacheMap.containsKey(key); + } + + protected Set getExpiredKeys() { + return this.cacheMap.keySet().stream() + .filter(this::isExpired) + .collect(Collectors.toSet()); + } + + protected boolean isExpired(K key) { + LocalDateTime expirationDateTime = this.cacheMap.get(key).getCreatedAt().plus(this.cacheTimeout, ChronoUnit.MILLIS); + return LocalDateTime.now().isAfter(expirationDateTime); + } + + public void clear() { + this.cacheMap = new HashMap<>(); + } + + public Optional get(K key) { + this.clean(); + return Optional.ofNullable(this.cacheMap.get(key)).map(CacheValue::getValue); + } + + public void put(K key, V value) { + this.cacheMap.put(key, this.createCacheValue(value)); + } + + protected CacheValue createCacheValue(V value) { + LocalDateTime now = LocalDateTime.now(); + return new CacheValue() { + @Override + public V getValue() { + return value; + } + + @Override + public LocalDateTime getCreatedAt() { + return now; + } + }; + } + + public void remove(K key) { + this.cacheMap.remove(key); + } + + protected interface CacheValue { + + V getValue(); + + LocalDateTime getCreatedAt(); + } + +} diff --git a/php/php.blade/src/org/netbeans/modules/php/blade/editor/completion/BladeCompletionHandler.java b/php/php.blade/src/org/netbeans/modules/php/blade/editor/completion/BladeCompletionHandler.java new file mode 100644 index 000000000000..377d8afdd6f5 --- /dev/null +++ b/php/php.blade/src/org/netbeans/modules/php/blade/editor/completion/BladeCompletionHandler.java @@ -0,0 +1,441 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.php.blade.editor.completion; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.Callable; +import java.util.logging.Logger; +import org.netbeans.editor.BaseDocument; +import javax.swing.text.Document; +import javax.swing.text.JTextComponent; +import org.netbeans.api.annotations.common.NonNull; +import org.netbeans.api.lexer.TokenHierarchy; +import org.netbeans.api.lexer.TokenSequence; +import org.netbeans.api.project.Project; +import org.netbeans.modules.csl.api.CodeCompletionContext; +import org.netbeans.modules.csl.api.CodeCompletionHandler; +import org.netbeans.modules.csl.api.CodeCompletionHandler2; +import org.netbeans.modules.csl.api.CodeCompletionResult; +import org.netbeans.modules.csl.api.CompletionProposal; +import org.netbeans.modules.csl.api.Documentation; +import org.netbeans.modules.csl.api.ElementHandle; +import org.netbeans.modules.csl.api.OffsetRange; +import org.netbeans.modules.csl.api.ParameterInfo; +import org.netbeans.modules.csl.spi.DefaultCompletionResult; +import org.netbeans.modules.csl.spi.ParserResult; +import org.netbeans.modules.csl.spi.support.CancelSupport; +import org.netbeans.modules.php.blade.csl.elements.DirectiveElement; +import org.netbeans.modules.php.blade.csl.elements.ElementType; +import org.netbeans.modules.php.blade.csl.elements.NamedElement; +import org.netbeans.modules.php.blade.csl.elements.PathElement; +import org.netbeans.modules.php.blade.csl.elements.PhpFunctionElement; +import org.netbeans.modules.php.blade.editor.BladeLanguage; +import org.netbeans.modules.php.blade.editor.directives.CustomDirectives; +import org.netbeans.modules.php.blade.editor.indexing.BladeIndex; +import org.netbeans.modules.php.blade.editor.lexer.BladeLexerUtils; +import org.netbeans.modules.php.blade.editor.lexer.BladeTokenId; +import static org.netbeans.modules.php.blade.editor.lexer.BladeTokenId.BLADE_DIRECTIVE_UNKNOWN; +import org.netbeans.modules.php.blade.editor.parser.BladeDirectiveScope; +import org.netbeans.modules.php.blade.editor.parser.BladeParserResult; +import org.netbeans.modules.php.blade.editor.path.BladePathUtils; +import org.netbeans.modules.php.blade.project.AssetsBundlerSupport; +import org.netbeans.modules.php.blade.project.ProjectUtils; +import org.netbeans.modules.php.blade.syntax.DirectivesList; +import org.netbeans.modules.php.blade.syntax.StringUtils; +import org.netbeans.modules.php.blade.syntax.ViewPathUtils; +import org.netbeans.modules.php.blade.syntax.annotation.Directive; +import static org.netbeans.modules.php.blade.syntax.antlr4.v10.BladeAntlrLexer.*; +import org.openide.filesystems.FileObject; +import org.openide.util.Exceptions; + +/** + * + * @author bogdan + */ +public class BladeCompletionHandler implements CodeCompletionHandler2 { + + private static final Logger LOGGER = Logger.getLogger(BladeCompletionHandler.class.getName()); + private static final String AT = "@"; //NOI18N + private static final String BLADE_LOOP_VAR = "$loop"; //NOI18N + + @Override + public CodeCompletionResult complete(CodeCompletionContext completionContext) { + if (CancelSupport.getDefault().isCancelled()) { + return CodeCompletionResult.NONE; + } + long startTime = System.currentTimeMillis(); + BaseDocument doc = (BaseDocument) completionContext.getParserResult().getSnapshot().getSource().getDocument(false); + + if (doc == null) { + return CodeCompletionResult.NONE; + } + + int offset = completionContext.getCaretOffset(); + + if (offset < 1) { + return CodeCompletionResult.NONE; + } + + BladeParserResult parserResult = (BladeParserResult) completionContext.getParserResult(); + + final TokenHierarchy th = parserResult.getSnapshot().getTokenHierarchy(); + + if (th == null) { + return CodeCompletionResult.NONE; + } + + TokenSequence ts = BladeLexerUtils.getBladeTokenSequenceDoc(th, offset); + + if (ts == null) { + return CodeCompletionResult.NONE; + } + + ts.move(offset); + + if (!ts.moveNext() && !ts.movePrevious()) { + return CodeCompletionResult.NONE; + } + + FileObject fo = parserResult.getSnapshot().getSource().getFileObject(); + + final List completionProposals = new ArrayList<>(); + + OffsetRange phpExprRange = parserResult.getBladePhpExpressionOccurences() + .findPhpExpressionLocation(offset); + + String contextPrefix = completionContext.getPrefix(); + + if (contextPrefix.startsWith(AT) && phpExprRange == null) { + completeDirectives(completionProposals, offset, fo, contextPrefix); + return new DefaultCompletionResult(completionProposals, false); + } else if (phpExprRange != null) { + BladeParserResult.BladeStringReference reference = parserResult.getBladeReferenceIdsCollection() + .findOccuredRefrence(offset); + + if (reference != null) { + completeBladeReference(completionProposals, fo, reference, offset); + } else if (isNotPhpAssignmentExpr(contextPrefix)) { + BladeDirectiveScope scope = parserResult.getBladeScope().findScope(offset); + + if (scope != null) { + int anchorOffset = computeAnchorOffset(contextPrefix, offset); + for (String variableName : scope.getScopeVariables()) { + if (variableName.startsWith(contextPrefix)) { + NamedElement variableElement = new NamedElement(variableName, fo, ElementType.VARIABLE); + completionProposals.add(new BladeCompletionProposal.VariableItem(variableElement, anchorOffset, variableName)); + } + } + + if (scope.getScopeType() == D_FOREACH && BLADE_LOOP_VAR.startsWith(contextPrefix)) { //NOI18N + NamedElement variableElement = new NamedElement(BLADE_LOOP_VAR, fo, ElementType.VARIABLE); + completionProposals.add(new BladeCompletionProposal.VariableItem(variableElement, anchorOffset, BLADE_LOOP_VAR)); + } + } + } else { + CharSequence snapshotExpr = completionContext.getParserResult().getSnapshot().getText().subSequence(phpExprRange.getStart(), phpExprRange.getEnd()); + PhpCodeCompletionService.completePhpCode(completionProposals, + snapshotExpr.toString(), phpExprRange.getStart(), offset, fo); + } + } + + if (completionProposals.isEmpty()) { + return CodeCompletionResult.NONE; + } + + long time = System.currentTimeMillis() - startTime; + if (time > 2000) { + LOGGER.info(String.format("complete() with results took %d ms", time)); //NOI18N + } + return new DefaultCompletionResult(completionProposals, false); + } + + private boolean isNotPhpAssignmentExpr(String contextPrefix){ + return contextPrefix.startsWith("$") && !contextPrefix.contains("="); //NOI18N + } + + private void completeBladeReference(final List completionProposals, + FileObject fo, BladeParserResult.BladeStringReference reference, int offset) { + switch (reference.antlrTokentype) { + case D_EXTENDS, D_INCLUDE, D_INCLUDE_IF, D_INCLUDE_WHEN, D_INCLUDE_UNLESS, D_INCLUDE_FIRST, D_EACH -> { + completeViewPath(completionProposals, reference.identifier, fo, offset); + } + case D_SECTION, D_HAS_SECTION, D_SECTION_MISSING -> { + String yieldId = reference.identifier; + completeYieldIdFromIndex(completionProposals, yieldId, fo, offset); + } + case D_PUSH, D_PUSH_IF, D_PUSH_ONCE, D_PREPEND -> { + String stackId = reference.identifier; + completeStackIdFromIndex(completionProposals, stackId, fo, offset); + } + case D_VITE -> { + String assetPath = reference.identifier; + completeResourcePath(completionProposals, assetPath, fo, offset); + } + } + } + + private void completeViewPath(final List completionProposals, + String pathName, FileObject fo, int offset) { + int pathOffset = ViewPathUtils.getViewPathSeparatorOffset(pathName, offset); + List childrenFiles = BladePathUtils.getParentChildrenFromPrefixPath(fo, pathName); + for (FileObject file : childrenFiles) { + String pathFileName = file.getName(); + boolean isFolder = file.isFolder(); + if (!isFolder) { + pathFileName = pathFileName.replace(BladeLanguage.FILE_EXTENSION_SUFFIX, ""); // NOI18N + } + PathElement pathEl = new PathElement(pathFileName, file); + completionProposals.add(new BladeCompletionProposal.ViewPathProposal(pathEl, pathOffset, pathFileName, isFolder)); + } + } + + private void completeYieldIdFromIndex(final List completionProposals, + String prefixIdentifier, FileObject fo, int offset) { + BladeIndex bladeIndex; + Project project = ProjectUtils.getMainOwner(fo); + int anchorOffset = computeAnchorOffset(prefixIdentifier, offset); + + try { + bladeIndex = BladeIndex.get(project); + List indexedReferences = bladeIndex.queryYieldIds(prefixIdentifier); + for (BladeIndex.IndexedReferenceId indexReference : indexedReferences) { + NamedElement yieldIdEl = new NamedElement(indexReference.getIdenfiier(), fo, ElementType.YIELD_ID); + completionProposals.add(new BladeCompletionProposal.LayoutIdentifierProposal(yieldIdEl, anchorOffset, indexReference.getIdenfiier())); + } + } catch (IOException ex) { + Exceptions.printStackTrace(ex); + } + } + + private void completeStackIdFromIndex(final List completionProposals, + String prefixIdentifier, FileObject fo, int offset) { + BladeIndex bladeIndex; + Project project = ProjectUtils.getMainOwner(fo); + int anchorOffset = computeAnchorOffset(prefixIdentifier, offset); + + try { + bladeIndex = BladeIndex.get(project); + List indexedReferences = bladeIndex.queryStacksIndexedReferences(prefixIdentifier); + for (BladeIndex.IndexedReferenceId indexReference : indexedReferences) { + NamedElement yieldIdEl = new NamedElement(indexReference.getIdenfiier(), fo, ElementType.STACK_ID); + completionProposals.add(new BladeCompletionProposal.LayoutIdentifierProposal(yieldIdEl, anchorOffset, indexReference.getIdenfiier())); + } + } catch (IOException ex) { + Exceptions.printStackTrace(ex); + } + } + + private void completeResourcePath(final List completionProposals, + String pathPrefix, FileObject fo, int offset) { + + FileObject projectDir = ProjectUtils.getProjectDirectory(fo); + + if (projectDir == null) { + return; + } + + FileObject resourceDir = projectDir.getFileObject(AssetsBundlerSupport.RESOURCE_ROOT); + + if (resourceDir == null) { + return; + } + + int firstSlash = pathPrefix.indexOf(StringUtils.FORWARD_SLASH); + int lastSlash = pathPrefix.lastIndexOf(StringUtils.FORWARD_SLASH); + + if (firstSlash == -1 && AssetsBundlerSupport.RESOURCE_ROOT.startsWith(pathPrefix)) { + + int anchorOffset = computeAnchorOffset(pathPrefix, offset); + for (FileObject file : resourceDir.getChildren()) { + if (!file.isFolder() || file.getName().equals(BladePathUtils.LARAVEL_VIEW_FOLDER)) { + continue; + } + + String proposedFolder = AssetsBundlerSupport.RESOURCE_ROOT + StringUtils.FORWARD_SLASH + file.getName(); + PathElement pathEl = new PathElement(proposedFolder, file); + completionProposals.add(new BladeCompletionProposal.FilePathProposal(pathEl, anchorOffset, proposedFolder, true)); + } + } else { + int anchorOffset = computeAnchorOffset(pathPrefix, offset) + lastSlash + 1; + List fileList = BladePathUtils.filterFilesFromRootFolder(new FileObject[]{projectDir}, pathPrefix, lastSlash); + for (FileObject file : fileList) { + String proposedPath = file.getNameExt(); + PathElement pathEl = new PathElement(proposedPath, file); + completionProposals.add(new BladeCompletionProposal.FilePathProposal(pathEl, anchorOffset, proposedPath, file.isFolder())); + } + } + } + + private void completeDirectives(final List completionProposals, + int caretOffset, FileObject fo, String prefix) { + DirectivesList listClass = new DirectivesList(); + + int anchorOffset = computeAnchorOffset(prefix, caretOffset); + + for (Directive directive : listClass.getDirectives()) { + String directiveName = directive.name(); + if (directiveName.startsWith(prefix)) { + DirectiveElement directiveEl = new DirectiveElement(directiveName, fo); + + if (directive.params()) { + completionProposals.add(new BladeCompletionProposal.DirectiveWithArg(directiveEl, anchorOffset, directive)); + if (!directive.endtag().isEmpty()) { + completionProposals.add(new BladeCompletionProposal.BlockDirectiveWithArg(directiveEl, anchorOffset, directive)); + } + } else { + completionProposals.add(new BladeCompletionProposal.InlineDirective(directiveEl, anchorOffset, directive)); + if (!directive.endtag().isEmpty()) { + completionProposals.add(new BladeCompletionProposal.BlockDirective(directiveEl, anchorOffset, directive)); + } + } + } + } + + Project project = ProjectUtils.getMainOwner(fo); + CustomDirectives.getInstance(project).filterAction(new CustomDirectives.FilterCallback() { + @Override + public void filterDirectiveName(CustomDirectives.CustomDirective directive, FileObject file) { + DirectiveElement directiveEl = new DirectiveElement(directive.getName(), file); + if (directive.getName().startsWith(prefix)) { + int anchorOffset = computeAnchorOffset(prefix, caretOffset); + completionProposals.add( + new BladeCompletionProposal.CustomDirective( + directiveEl, + anchorOffset, + directive.getName() + )); + } + } + }); + } + + @Override + public String document(ParserResult pr, ElementHandle eh) { + return null; + } + + @Override + public ElementHandle resolveLink(String string, ElementHandle eh) { + return null; + } + + @Override + public String getPrefix(ParserResult info, int offset, boolean upToOffset) { + + BaseDocument document = (BaseDocument) info.getSnapshot().getSource().getDocument(false); + + if (document == null) { + return null; + } + try { + document.readLock(); + TokenSequence ts = BladeLexerUtils.getTokenSequence(document, offset); + + if (ts == null) { + return null; + } + + ts.move(offset); + + if (!ts.moveNext() && !ts.movePrevious()) { + return null; + } + org.netbeans.api.lexer.Token token = ts.token(); + + String tokenPrefix = token.text().toString().trim(); + BladeTokenId tokenId = token.id(); + if (tokenId.equals(BLADE_DIRECTIVE_UNKNOWN)) { + //sanitize adiacent emebedding hack to trigger blade completion + //ex: "@$caret" or @$caret> + if (tokenPrefix.endsWith("\"") || tokenPrefix.endsWith(">")) { // NOI18N + return tokenPrefix.substring(0, tokenPrefix.length() - 1); + } + } + return tokenPrefix; + } finally { + document.readUnlock(); + } + } + + @Override + public CodeCompletionHandler.QueryType getAutoQuery(JTextComponent component, String typedText) { + if (typedText.length() == 0) { + return CodeCompletionHandler.QueryType.NONE; + } + + if (typedText.startsWith(AT)) { + return CodeCompletionHandler.QueryType.ALL_COMPLETION; + } + + char lastChar = typedText.charAt(typedText.length() - 1); + + switch (lastChar) { + case '\n': + return CodeCompletionHandler.QueryType.STOP; + default: + return CodeCompletionHandler.QueryType.ALL_COMPLETION; + } + } + + @Override + @SuppressWarnings("rawtypes") + public String resolveTemplateVariable(String string, ParserResult pr, int i, String string1, Map map) { + return null; + } + + @Override + public Set getApplicableTemplates(Document dcmnt, int i, int i1) { + return Collections.emptySet(); + } + + @Override + public ParameterInfo parameters(ParserResult pr, int i, CompletionProposal cp) { + return new ParameterInfo(new ArrayList<>(), 0, 0); + } + + /** + * used also for tooltip in blade mime context + * + * @param parserResult + * @param elementHandle + * @param cancel + * @return + */ + @Override + public Documentation documentElement(ParserResult parserResult, ElementHandle elementHandle, Callable cancel) { + Documentation result = null; + if (elementHandle instanceof PhpFunctionElement) { + return TooltipDoc.generateFunctionDoc((PhpFunctionElement) elementHandle); + } else if (elementHandle instanceof DirectiveElement) { + return result; + } else if (elementHandle instanceof NamedElement) { + return TooltipDoc.generateDoc((NamedElement) elementHandle); + } + return result; + } + + private int computeAnchorOffset(@NonNull String prefix, int offset) { + return offset - prefix.length(); + } +} diff --git a/php/php.blade/src/org/netbeans/modules/php/blade/editor/completion/BladeCompletionProposal.java b/php/php.blade/src/org/netbeans/modules/php/blade/editor/completion/BladeCompletionProposal.java new file mode 100644 index 000000000000..89556b4ad24f --- /dev/null +++ b/php/php.blade/src/org/netbeans/modules/php/blade/editor/completion/BladeCompletionProposal.java @@ -0,0 +1,550 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.php.blade.editor.completion; + +import java.util.Collections; +import java.util.Set; +import javax.swing.ImageIcon; +import org.netbeans.modules.csl.api.CompletionProposal; +import org.netbeans.modules.csl.api.ElementHandle; +import org.netbeans.modules.csl.api.ElementKind; +import org.netbeans.modules.csl.api.HtmlFormatter; +import org.netbeans.modules.csl.api.Modifier; +import org.netbeans.modules.php.blade.csl.elements.ClassElement; +import org.netbeans.modules.php.blade.editor.EditorStringUtils; +import org.netbeans.modules.php.blade.editor.ResourceUtilities; +import org.netbeans.modules.php.blade.syntax.BladeDirectivesUtils; +import org.netbeans.modules.php.blade.syntax.annotation.Directive; +import org.netbeans.modules.php.blade.syntax.annotation.Tag; +import org.openide.filesystems.FileObject; +import org.openide.util.ImageUtilities; + +/** + * + * @author bogdan + */ +public class BladeCompletionProposal implements CompletionProposal { + + private final ElementHandle element; + private final String previewValue; + private int anchorOffset; + private Directive directive; + + public BladeCompletionProposal(ElementHandle element, int anchorOffset, String previewValue) { + this.element = element; + this.anchorOffset = anchorOffset; + this.previewValue = previewValue; + } + + public BladeCompletionProposal(ElementHandle element, int anchorOffset, Directive directive) { + this.element = element; + this.anchorOffset = anchorOffset; + this.previewValue = directive.name(); + this.directive = directive; + } + + @Override + public int getAnchorOffset() { + return anchorOffset; + } + + @Override + public ElementHandle getElement() { + return element; + } + + @Override + public String getName() { + return element.getName(); + } + + @Override + public String getSortText() { + return getName(); + } + + @Override + public int getSortPrioOverride() { + return 0; + } + + @Override + public String getLhsHtml(HtmlFormatter formatter) { + formatter.name(getKind(), true); + formatter.appendHtml(""); // NOI18N + formatter.appendHtml(""); // NOI18N + formatter.appendText(previewValue); + formatter.appendHtml(""); // NOI18N + formatter.appendHtml(""); // NOI18N + formatter.name(getKind(), false); // NOI18N + return formatter.getText(); + } + + @Override + public ImageIcon getIcon() { + return null; + } + + @Override + public Set getModifiers() { + return Collections.emptySet(); + } + + @Override + public String getCustomInsertTemplate() { + return null; + } + + @Override + public String getInsertPrefix() { + StringBuilder template = new StringBuilder(); + template.append(getName()); + return template.toString(); + + } + + @Override + public String getRhsHtml(HtmlFormatter formatter) { + FileObject file = null; + if (element != null) { + file = element.getFileObject(); + } + if (file != null) { + formatter.reset(); + formatter.appendText(" "); // NOI18N + formatter.appendText(file.getName()); + } + return formatter.getText(); + } + + @Override + public ElementKind getKind() { + return ElementKind.CONSTRUCTOR; + } + + @Override + public boolean isSmart() { + return true; + } + + public String getPreviewValue() { + return previewValue; + } + + public Directive getDirective() { + return directive; + } + + public static class PhpElementItem extends BladeCompletionProposal { + + public PhpElementItem(ElementHandle element, int anchorOffset, String previewValue) { + super(element, anchorOffset, previewValue); + } + + @Override + public String getRhsHtml(HtmlFormatter formatter) { + FileObject file = null; + if (this.getElement() != null) { + file = this.getElement().getFileObject(); + } + if (file != null) { + formatter.reset(); + formatter.appendText(" "); // NOI18N + formatter.appendText(file.getNameExt()); + } + return formatter.getText(); + } + } + + public static class NamespaceItem extends PhpElementItem { + + public NamespaceItem(ElementHandle element, int anchorOffset, String previewValue) { + super(element, anchorOffset, previewValue); + } + + @Override + public ElementKind getKind() { + return ElementKind.PACKAGE; + } + + @Override + public int getSortPrioOverride() { + return -50;//priority + } + + @Override + public String getCustomInsertTemplate() { + String insertText = getElement().getName(); + return insertText; + } + + @Override + public String getRhsHtml(HtmlFormatter formatter) { + String info = getElement().getName(); + + if (info.startsWith(EditorStringUtils.NAMESPACE_SEPARATOR)){ + info = info.substring(1); + } + int slashPos = info.indexOf(EditorStringUtils.NAMESPACE_SEPARATOR); + if (slashPos > -1){ + return info.substring(0, slashPos); + } else { + return info; + } + } + } + + public static class DirectiveItem extends BladeCompletionProposal { + + public DirectiveItem(ElementHandle element, int anchorOffset, String previewValue) { + super(element, anchorOffset, previewValue); + } + + } + + public static class ClassItem extends PhpElementItem { + + private String namespace = null; + private final boolean autoCompleteNamespace; + + public ClassItem(ClassElement element, int anchorOffset, String previewValue, boolean autoCompleteNamespace) { + super(element, anchorOffset, previewValue); + this.namespace = element.getNamespace(); + this.autoCompleteNamespace = autoCompleteNamespace; + } + + @Override + public ElementKind getKind() { + return ElementKind.CLASS; + } + + @Override + public String getRhsHtml(HtmlFormatter formatter) { + if (namespace != null && namespace.length() > 0) { + return namespace; + } + return super.getRhsHtml(formatter); + } + + @Override + public int getSortPrioOverride() { + return 10;//priority + } + + @Override + public String getCustomInsertTemplate() { + if (autoCompleteNamespace == true && namespace != null && namespace.length() > 0) { + return EditorStringUtils.NAMESPACE_SEPARATOR + namespace + EditorStringUtils.NAMESPACE_SEPARATOR + getElement().getName(); // NOI18N + } + + String insertText = getElement().getName(); + return insertText; + } + } + + public static class FunctionItem extends PhpElementItem { + + protected final String namespace; + + public FunctionItem(ElementHandle element, int anchorOffset, String previewValue) { + super(element, anchorOffset, previewValue); + this.namespace = null; + } + + public FunctionItem(ElementHandle element, int anchorOffset, + String namespace, + String previewValue) { + super(element, anchorOffset, previewValue); + this.namespace = namespace; + } + + @Override + public ElementKind getKind() { + return ElementKind.METHOD; + } + + @Override + public int getSortPrioOverride() { + return 20;//priority + } + + @Override + public String getRhsHtml(HtmlFormatter formatter) { + if (namespace != null && namespace.length() > 0) { + return namespace; + } + return super.getRhsHtml(formatter); + } + + } + + public static class ConstantItem extends PhpElementItem { + + public ConstantItem(ElementHandle element, int anchorOffset, String previewValue) { + super(element, anchorOffset, previewValue); + } + + @Override + public ElementKind getKind() { + return ElementKind.CONSTANT; + } + + } + + public static class VariableItem extends BladeCompletionProposal { + + public VariableItem(ElementHandle element, int anchorOffset, String previewValue) { + super(element, anchorOffset, previewValue); + } + + @Override + public ElementKind getKind() { + return ElementKind.VARIABLE; + } + + } + + public static class BladeVariableItem extends BladeCompletionProposal { + + public BladeVariableItem(ElementHandle element, int anchorOffset, String previewValue) { + super(element, anchorOffset, previewValue); + } + + @Override + public ElementKind getKind() { + return ElementKind.VARIABLE; + } + + @Override + public String getRhsHtml(HtmlFormatter formatter) { + return "blade"; // NOI18N + } + } + + public static class BladeTag extends BladeCompletionProposal { + + private Tag tag; + + public BladeTag(ElementHandle element, int anchorOffset, Tag tag) { + super(element, anchorOffset, ""); // NOI18N + this.tag = tag; + } + + @Override + public String getCustomInsertTemplate() { + return tag.openTag() + " ${cursor} " + tag.closeTag(); // NOI18N + } + + @Override + public String getLhsHtml(HtmlFormatter formatter) { + return tag.openTag() + " " + tag.closeTag(); // NOI18N + } + + @Override + public String getRhsHtml(HtmlFormatter formatter) { + return tag.description(); + } + + @Override + public int getSortPrioOverride() { + return 0; + } + } + + public static class LayoutIdentifierProposal extends BladeCompletionProposal { + + public LayoutIdentifierProposal(ElementHandle element, int anchorOffset, String previewValue) { + super(element, anchorOffset, previewValue); + } + + @Override + public ImageIcon getIcon() { + String path = ResourceUtilities.LAYOUT_IDENTIFIER; + return ImageUtilities.loadImageIcon(path, false); + } + } + + public static class ViewPathProposal extends BladeCompletionProposal { + + private final boolean isFolder; + + public ViewPathProposal(ElementHandle element, int anchorOffset, String previewValue, boolean isFolder) { + super(element, anchorOffset, previewValue); + this.isFolder = isFolder; + } + + @Override + public ImageIcon getIcon() { + String path = ResourceUtilities.BLADE_VIEW; + if (isFolder) { + path = ResourceUtilities.FOLDER; + } + return ImageUtilities.loadImageIcon(path, false); + } + } + + public static class FilePathProposal extends BladeCompletionProposal { + + private final boolean isFolder; + + public FilePathProposal(ElementHandle element, int anchorOffset, String previewValue, boolean isFolder) { + super(element, anchorOffset, previewValue); + this.isFolder = isFolder; + } + + @Override + public ImageIcon getIcon() { + String path = ResourceUtilities.BLADE_VIEW; + if (isFolder) { + path = ResourceUtilities.FOLDER; + } else if (getPreviewValue().endsWith(".css")) { // NOI18N + path = ResourceUtilities.CSS_FILE; + } else if (getPreviewValue().endsWith(".js")) { // NOI18N + path = ResourceUtilities.JS_FILE; + } + return ImageUtilities.loadImageIcon(path, false); + } + } + + public static class DirectiveProposal extends BladeCompletionProposal { + + public DirectiveProposal(ElementHandle element, int anchorOffset, Directive directive) { + super(element, anchorOffset, directive); + } + + public DirectiveProposal(ElementHandle element, int anchorOffset, String previewValue) { + super(element, anchorOffset, previewValue); + } + + @Override + public ImageIcon getIcon() { + String path = ResourceUtilities.DIRECTIVE_ICON; + return ImageUtilities.loadImageIcon(path, false); + } + + @Override + public String getRhsHtml(HtmlFormatter formatter) { + if (this.getDirective() == null) { + return null; + } + + if (getDirective().description().isEmpty() && !getDirective().since().isEmpty()) { + return "v" + getDirective().since(); // NOI18N + } + return getDirective().description(); + } + + } + + public static class CustomDirective extends DirectiveProposal { + + public CustomDirective(ElementHandle element, int anchorOffset, String preview) { + super(element, anchorOffset, preview); + } + + @Override + public String getRhsHtml(HtmlFormatter formatter) { + if (this.getElement().getFileObject() != null) { + return this.getElement().getFileObject().getNameExt(); + } + return "custom directive"; // NOI18N + } + + } + + public static class InlineDirective extends DirectiveProposal { + + public InlineDirective(ElementHandle element, int anchorOffset, Directive directive) { + super(element, anchorOffset, directive); + } + + } + + public static class DirectiveWithArg extends InlineDirective { + + public DirectiveWithArg(ElementHandle element, int anchorOffset, Directive directive) { + super(element, anchorOffset, directive); + } + + @Override + public String getCustomInsertTemplate() { + switch (getName()) { + case BladeDirectivesUtils.DIRECTIVE_INCLUDE: + case BladeDirectivesUtils.DIRECTIVE_EXTENDS: { + return getName() + "('${path}')"; // NOI18N + } + default: { + return getName() + "($$${arg})"; // NOI18N + } + } + } + + @Override + public String getLhsHtml(HtmlFormatter formatter) { + return getName() + "()"; // NOI18N + } + } + + public static class BlockDirective extends DirectiveProposal { + + public BlockDirective(ElementHandle element, int anchorOffset, Directive directive) { + super(element, anchorOffset, directive); + } + + @Override + public String getLhsHtml(HtmlFormatter formatter) { + return getName() + " ... " + getDirective().endtag(); // NOI18N + } + + @Override + public String getCustomInsertTemplate() { + return getName() + "\n ${selection} ${cursor}\n" + getDirective().endtag(); // NOI18N + } + + } + + public static class BlockDirectiveWithArg extends DirectiveProposal { + + public BlockDirectiveWithArg(ElementHandle element, int anchorOffset, Directive directive) { + super(element, anchorOffset, directive); + } + + @Override + public String getLhsHtml(HtmlFormatter formatter) { + return getName() + "() ... " + getDirective().endtag(); // NOI18N + } + + @Override + public String getCustomInsertTemplate() { + switch (getName()) { + case BladeDirectivesUtils.DIRECTIVE_FOREACH: { + return getName() + "($$${array} as $$${item})\n ${selection}${cursor}\n" + getDirective().endtag(); // NOI18N + } + case BladeDirectivesUtils.DIRECTIVE_SECTION: + case BladeDirectivesUtils.DIRECTIVE_SESSION: { + return getName() + "('${id}')\n ${cursor}\n" + getDirective().endtag(); // NOI18N + } + default: { + return getName() + "($$${arg})\n ${cursor}\n" + getDirective().endtag(); // NOI18N + } + } + } + + } +} diff --git a/php/php.blade/src/org/netbeans/modules/php/blade/editor/completion/PhpCodeCompletionService.java b/php/php.blade/src/org/netbeans/modules/php/blade/editor/completion/PhpCodeCompletionService.java new file mode 100644 index 000000000000..91bb0e9d2870 --- /dev/null +++ b/php/php.blade/src/org/netbeans/modules/php/blade/editor/completion/PhpCodeCompletionService.java @@ -0,0 +1,389 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.php.blade.editor.completion; + +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Set; +import javax.swing.ImageIcon; +import org.netbeans.api.annotations.common.NonNull; +import org.netbeans.api.annotations.common.NullAllowed; +import org.netbeans.api.project.Project; +import org.netbeans.modules.csl.api.CompletionProposal; +import org.netbeans.modules.csl.api.ElementHandle; +import org.netbeans.modules.csl.api.ElementKind; +import org.netbeans.modules.csl.api.HtmlFormatter; +import org.netbeans.modules.csl.api.Modifier; +import org.netbeans.modules.php.blade.csl.elements.ClassElement; +import org.netbeans.modules.php.blade.csl.elements.ElementType; +import org.netbeans.modules.php.blade.csl.elements.NamedElement; +import org.netbeans.modules.php.blade.csl.elements.PhpKeywordElement; +import org.netbeans.modules.php.blade.editor.EditorStringUtils; +import org.netbeans.modules.php.blade.editor.indexing.PhpIndexFunctionResult; +import org.netbeans.modules.php.blade.editor.indexing.PhpIndexResult; +import org.netbeans.modules.php.blade.editor.indexing.PhpIndexUtils; +import org.netbeans.modules.php.blade.syntax.annotation.PhpKeyword; +import org.netbeans.modules.php.blade.syntax.antlr4.php.BladePhpAntlrLexer; +import org.netbeans.modules.php.blade.syntax.antlr4.php.BladePhpAntlrUtils; +import org.netbeans.modules.php.blade.editor.parser.BladePhpSnippetParser; +import static org.netbeans.modules.php.blade.editor.parser.BladePhpSnippetParser.PhpReferenceType.PHP_NAMESPACE; +import org.netbeans.modules.php.blade.syntax.php.PhpKeywordList; +import org.openide.filesystems.FileObject; + +/** + * + * @author bogdan + */ +public class PhpCodeCompletionService { + + public static void completePhpCode(final List completionProposals, + String snapshotExpr, int exprStart, int offset, FileObject fo) { + int referencedOffset = offset - exprStart - 1; + + org.antlr.v4.runtime.Token targetetToken = BladePhpAntlrUtils.getToken(snapshotExpr, referencedOffset); + + if (targetetToken == null) { + return; + } + + int completionOffset = exprStart + targetetToken.getStartIndex(); + + String phpIdentifier = ""; // NOI18N + + if (targetetToken.getType() != BladePhpAntlrLexer.DOUBLE_COLON) { + phpIdentifier = targetetToken.getText(); + } else { + //double colon offset increase "::" + completionOffset += targetetToken.getText().length(); + } + + BladePhpSnippetParser phpSnippetParser = new BladePhpSnippetParser(snapshotExpr, fo, exprStart); + phpSnippetParser.parse(); + BladePhpSnippetParser.PhpReference phpRef = phpSnippetParser.findIdentifierReference(referencedOffset); + BladePhpSnippetParser.FieldAcces fieldAccess = phpSnippetParser.findFieldAccessReference(referencedOffset); + + if (fieldAccess != null) { + if (fieldAccess.owner.namespace == null) { + completeClassConstants(completionProposals, phpIdentifier, fieldAccess.owner.identifier, completionOffset, fo); + } + completeClassMethods(completionProposals, phpIdentifier, fieldAccess, completionOffset, fo); + } else if (phpRef != null) { + if (phpRef.namespace != null){ + boolean globalNamespace = phpRef.namespace.startsWith(EditorStringUtils.NAMESPACE_SEPARATOR); + String namespaceQuery = globalNamespace ? phpRef.namespace.substring(1) : phpRef.namespace; + + if (phpRef.identifier != null){ + completeNamespaceClasses(completionProposals, phpRef.identifier, namespaceQuery, completionOffset, fo); + completeNamespace(completionProposals, namespaceQuery + EditorStringUtils.NAMESPACE_SEPARATOR + phpRef.identifier, completionOffset, fo); + } else { + completeNamespace(completionProposals, namespaceQuery, completionOffset, fo); + } + } else if (phpRef.type.equals(PHP_NAMESPACE)){ + completeAllRelativeNamespacesClasses(completionProposals, phpRef.identifier, offset, fo); + } + } else if (targetetToken.getType() == BladePhpAntlrLexer.IDENTIFIER) { + //no context but with identifier + completePhpKeywords(completionProposals, phpIdentifier, completionOffset); + completePhpFunctions(completionProposals, phpIdentifier, completionOffset, fo); + completePhpClasses(completionProposals, phpIdentifier, completionOffset, fo); + completeNamespace(completionProposals, phpIdentifier, completionOffset + 1, fo); + } + //add variable flow + + } + + private static void completePhpKeywords(final List completionProposals, + String prefix, int caretOffset) { + PhpKeywordList keywordList = new PhpKeywordList(); + for (PhpKeyword keyword : keywordList.getKeywords()) { + if (keyword.name().startsWith(prefix)) { + PhpKeywordElement keywordEl = new PhpKeywordElement(keyword.name()); + completionProposals.add(new PhpKeywordProposal(keywordEl, caretOffset)); + } + } + } + + private static void completePhpClasses(final List completionProposals, + String prefix, int offset, FileObject fo) { + + Collection indexClassResults = PhpIndexUtils.queryClass(fo, prefix); + + if (indexClassResults.isEmpty()) { + return; + } + + for (PhpIndexResult indexResult : indexClassResults) { + completionProposals.add(new BladeCompletionProposal.ClassItem( + classElement(indexResult), offset, indexResult.name, true)); + } + } + + private static void completePhpFunctions(final List completionProposals, + String prefix, int offset, FileObject fo) { + Collection indexedFunctions = PhpIndexUtils.queryFunctions( + fo, prefix); + if (indexedFunctions.isEmpty()) { + return; + } + + for (PhpIndexFunctionResult indexResult : indexedFunctions) { + String preview = indexResult.name + indexResult.getParamsAsString(); + completionProposals.add(new BladeCompletionProposal.FunctionItem( + functionElement(indexResult), + offset, + preview) + ); + } + } + + private static void completeNamespace(final List completionProposals, + String prefix, int offset, FileObject fo) { + + int substringOffset = prefix.startsWith(EditorStringUtils.NAMESPACE_SEPARATOR) ? 1 : 0; + + Collection indexClassResults = PhpIndexUtils.queryNamespace( + fo, prefix.substring(substringOffset) + ); + + if (indexClassResults.isEmpty()) { + return; + } + + int firstSeparator = Math.max(0, prefix.lastIndexOf(EditorStringUtils.NAMESPACE_SEPARATOR)); + int anchorOffset = offset - firstSeparator - 1; + for (PhpIndexResult indexResult : indexClassResults) { + if (!indexResult.name.startsWith(prefix)){ + continue; + } + completionProposals.add(new BladeCompletionProposal.NamespaceItem( + namespaceElement(indexResult), anchorOffset, indexResult.name)); + } + } + + private static void completeAllRelativeNamespacesClasses(final List completionProposals, + String prefix, int offset, FileObject fo) { + + int substringOffset = prefix.startsWith(EditorStringUtils.NAMESPACE_SEPARATOR) ? 1 : 0; + + Collection indexClassResults = PhpIndexUtils.queryAllNamespaceClasses( + fo, prefix.substring(substringOffset) + ); + + if (indexClassResults.isEmpty()) { + return; + } + + for (PhpIndexResult indexResult : indexClassResults) { + completionProposals.add(new BladeCompletionProposal.ClassItem( + classElement(indexResult), offset, indexResult.name, false)); + } + } + + private static void completeNamespaceClasses(final List completionProposals, + String prefix, String namespace, int offset, FileObject fo) { + + Collection indexClassResults = PhpIndexUtils.queryNamespaceClassesName(fo, prefix, namespace) ; + + if (indexClassResults.isEmpty()) { + return; + } + + for (PhpIndexResult indexResult : indexClassResults) { + completionProposals.add(new BladeCompletionProposal.ClassItem( + classElement(indexResult), offset, indexResult.name, false)); + } + } + + private static void completeClassConstants(final List completionProposals, + String prefix, String ownerClass, int offset, FileObject fo) { + + Collection indexClassResults = PhpIndexUtils.queryClassConstants( + fo, prefix, ownerClass); + + //treat only uppercase strings + if (prefix.length() > 0 && !Character.isUpperCase(prefix.charAt(0))) { + return; + } + + if (indexClassResults.isEmpty()) { + return; + } + + for (PhpIndexResult indexResult : indexClassResults) { + completionProposals.add(new BladeCompletionProposal.ConstantItem( + constantElement(indexResult), offset, indexResult.name)); + } + } + + /** + * warning this doesn't check visibility : public, private, protected + * + * @param completionProposals + * @param prefix + * @param fieldAccessReference + * @param offset + * @param fo + */ + private static void completeClassMethods(final List completionProposals, + String prefix, BladePhpSnippetParser.FieldAcces fieldAccessReference, + int offset, FileObject fo) { + Collection indexedFunctions = PhpIndexUtils.queryClassMethods( + fo, prefix, fieldAccessReference.owner.identifier, + fieldAccessReference.owner.namespace, fieldAccessReference.type); + + if (indexedFunctions.isEmpty()) { + return; + } + + for (PhpIndexFunctionResult indexResult : indexedFunctions) { + String preview = indexResult.name + indexResult.getParamsAsString(); + completionProposals.add(new BladeCompletionProposal.FunctionItem( + functionElement(indexResult), + offset, + indexResult.getClassNamespace(), + preview) + ); + } + } + + private static ClassElement classElement(PhpIndexResult indexResult) { + return new ClassElement(indexResult.name, + indexResult.namespace, + indexResult.declarationFile); + } + + private static NamedElement namespaceElement(PhpIndexResult indexResult) { + return namedElement(indexResult, ElementType.PHP_NAMESPACE); + } + + private static NamedElement functionElement(PhpIndexResult indexResult) { + String inputString = indexResult.name + "()"; // NOI18N + return namedElement(inputString, indexResult, ElementType.PHP_FUNCTION); + } + + private static NamedElement constantElement(PhpIndexResult indexResult) { + return namedElement(indexResult, ElementType.PHP_CONSTANT); + } + + private static NamedElement namedElement(PhpIndexResult indexResult, ElementType type) { + return new NamedElement(indexResult.name, indexResult.declarationFile, type); + } + + private static NamedElement namedElement(String preview, PhpIndexResult indexResult, ElementType type) { + return new NamedElement(preview, indexResult.declarationFile, type); + } + + public static int computeAnchorOffset(@NonNull String prefix, int offset) { + return offset - prefix.length(); + } + + public abstract static class PhpCompletionProposal implements CompletionProposal { + + private final ElementHandle element; + private final int anchorOffset; + @NullAllowed + private final String description; + + public PhpCompletionProposal(ElementHandle element, int anchorOffset, String description) { + this.element = element; + this.anchorOffset = anchorOffset; + this.description = description; + } + + @Override + public int getAnchorOffset() { + return anchorOffset; + } + + @Override + public ElementHandle getElement() { + return element; + } + + @Override + public String getName() { + return element.getName(); + } + + @Override + public String getSortText() { + return getName(); + } + + @Override + public int getSortPrioOverride() { + return 0; + } + + @Override + public String getLhsHtml(HtmlFormatter formatter) { + return getName(); + } + + @Override + public ImageIcon getIcon() { + return null; + } + + @Override + public Set getModifiers() { + return Collections.emptySet(); + } + + @Override + public String getCustomInsertTemplate() { + return null; + } + + @Override + public String getInsertPrefix() { + return getName(); + + } + + @Override + public String getRhsHtml(HtmlFormatter formatter) { + return ""; // NOI18N + } + + @Override + public ElementKind getKind() { + return ElementKind.CONSTRUCTOR; + } + + @Override + public boolean isSmart() { + return true; + } + + public String getDescription() { + return description; + } + + } + + public static class PhpKeywordProposal extends PhpCompletionProposal { + + public PhpKeywordProposal(ElementHandle element, int anchorOffset) { + super(element, anchorOffset, null); + } + + } +} diff --git a/php/php.blade/src/org/netbeans/modules/php/blade/editor/completion/TooltipDoc.java b/php/php.blade/src/org/netbeans/modules/php/blade/editor/completion/TooltipDoc.java new file mode 100644 index 000000000000..f683be7d6b64 --- /dev/null +++ b/php/php.blade/src/org/netbeans/modules/php/blade/editor/completion/TooltipDoc.java @@ -0,0 +1,63 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.php.blade.editor.completion; + +import org.netbeans.modules.csl.api.Documentation; +import org.netbeans.modules.php.blade.csl.elements.NamedElement; +import org.netbeans.modules.php.blade.csl.elements.PhpFunctionElement; +import org.netbeans.modules.php.blade.editor.path.BladePathUtils; + +/** + * @TODO update doc representation + * + * @author bogdan + */ +public class TooltipDoc { + + public static Documentation generateDoc(NamedElement elementHandle) { + Documentation result = null; + switch (elementHandle.getType()) { + case PATH -> { + String filePath = ""; // NOI18N + if (elementHandle.getFileObject() != null){ + filePath = BladePathUtils.getRelativeProjectPath(elementHandle.getFileObject()); + } + return Documentation.create(String.format("
%s
", "blade path") // NOI18N + + "
" + filePath + "
", null); // NOI18N + } + case CUSTOM_DIRECTIVE -> { + String docInfo = String.format("
%s
", "custom directive") // NOI18N + + "
" + elementHandle.getFileObject().getNameExt() + "
"; // NOI18N + return Documentation.create(docInfo, null); + } + } + + return result; + } + + public static Documentation generateFunctionDoc(PhpFunctionElement elementHandle) { + String info = "
" + elementHandle.getName() + elementHandle.getParamsAsString() + "
"; // NOI18N + if (elementHandle.getNamespace() != null){ + info += "
" + elementHandle.getNamespace() + "
"; // NOI18N + } + info += "
" + elementHandle.getFileObject().getNameExt() + "
"; + info += String.format("
%s
", "php function"); // NOI18N + return Documentation.create(info, null); + } +} diff --git a/php/php.blade/src/org/netbeans/modules/php/blade/editor/completion/providers/BladeHtmlCompletionProvider.java b/php/php.blade/src/org/netbeans/modules/php/blade/editor/completion/providers/BladeHtmlCompletionProvider.java new file mode 100644 index 000000000000..04abf598ff58 --- /dev/null +++ b/php/php.blade/src/org/netbeans/modules/php/blade/editor/completion/providers/BladeHtmlCompletionProvider.java @@ -0,0 +1,281 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.php.blade.editor.completion.providers; + +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; +import java.util.logging.Level; +import java.util.logging.Logger; +import javax.swing.text.AbstractDocument; +import javax.swing.text.BadLocationException; +import javax.swing.text.JTextComponent; +import javax.swing.text.Document; +import org.antlr.v4.runtime.CharStreams; +import org.antlr.v4.runtime.Token; +import org.netbeans.api.editor.document.EditorDocumentUtils; +import org.netbeans.api.editor.mimelookup.MimeRegistration; +import org.netbeans.api.editor.mimelookup.MimeRegistrations; +import org.netbeans.api.project.Project; +import org.netbeans.modules.php.blade.editor.BladeLanguage; +import org.netbeans.modules.php.blade.editor.FileSystemUtils; +import static org.netbeans.modules.php.blade.editor.ResourceUtilities.CUSTOM_HTML_ICON; +import static org.netbeans.modules.php.blade.editor.ResourceUtilities.XML_ATTRIBUTE_ICON; +import org.netbeans.modules.php.blade.editor.components.ComponentModel; +import org.netbeans.modules.php.blade.editor.components.ComponentsQueryService; +import org.netbeans.modules.php.blade.editor.indexing.PhpIndexResult; +import org.netbeans.modules.php.blade.project.ComponentsSupport; +import org.netbeans.modules.php.blade.syntax.BladeTagsUtils; +import org.netbeans.modules.php.blade.syntax.StringUtils; +import org.netbeans.modules.php.blade.syntax.antlr4.html_components.BladeHtmlAntlrLexer; +import org.netbeans.modules.php.blade.syntax.antlr4.html_components.BladeHtmlAntlrUtils; +import org.netbeans.modules.php.editor.parser.astnodes.FormalParameter; +import org.netbeans.spi.editor.completion.CompletionItem; +import org.netbeans.spi.editor.completion.CompletionProvider; +import static org.netbeans.spi.editor.completion.CompletionProvider.COMPLETION_QUERY_TYPE; +import org.netbeans.spi.editor.completion.CompletionResultSet; +import org.netbeans.spi.editor.completion.CompletionTask; +import org.netbeans.spi.editor.completion.support.AsyncCompletionQuery; +import org.netbeans.spi.editor.completion.support.AsyncCompletionTask; +import org.netbeans.spi.editor.completion.support.CompletionUtilities; +import org.netbeans.spi.lexer.antlr4.AntlrTokenSequence; +import org.openide.filesystems.FileObject; + +/** + * + * @author bhaidu + */ +@MimeRegistrations(value = { + @MimeRegistration(mimeType = "text/html", service = CompletionProvider.class), + @MimeRegistration(mimeType = BladeLanguage.MIME_TYPE, service = CompletionProvider.class) +}) +public class BladeHtmlCompletionProvider implements CompletionProvider { + + private static final Logger LOGGER = Logger.getLogger(BladeHtmlCompletionProvider.class.getName()); + + @Override + public CompletionTask createTask(int queryType, JTextComponent component) { + return new AsyncCompletionTask(new BladeCompletionQuery(), component); + } + + @Override + public int getAutoQueryTypes(JTextComponent component, String typedText) { + FileObject fo = EditorDocumentUtils.getFileObject(component.getDocument()); + if (fo == null || !fo.getMIMEType().equals(BladeLanguage.MIME_TYPE)) { + return 0; + } + + if (typedText.length() == 0) { + return 0; + } + + //don't autocomplete on space, \n, ) + if (typedText.trim().isEmpty()) { + return 0; + } + + char lastChar = typedText.charAt(typedText.length() - 1); + switch (lastChar) { + case ')': + case '\n': + case '<': + case '>': + return 0; + } + return COMPLETION_QUERY_TYPE; + } + + private class BladeCompletionQuery extends AsyncCompletionQuery { + + public BladeCompletionQuery() { + } + + @Override + protected void query(CompletionResultSet resultSet, Document doc, int caretOffset) { + long startTime = System.currentTimeMillis(); + AbstractDocument adoc = (AbstractDocument) doc; + try { + FileObject fo = EditorDocumentUtils.getFileObject(doc); + + if (fo == null || !fo.getMIMEType().equals(BladeLanguage.MIME_TYPE)) { + return; + } + + adoc.readLock(); + AntlrTokenSequence tokens; + String typedText; + try { + String text = doc.getText(0, doc.getLength()); + typedText = doc.getText(caretOffset - 1, 1); + tokens = new AntlrTokenSequence(new BladeHtmlAntlrLexer(CharStreams.fromString(text))); + } catch (BadLocationException ex) { + return; + } finally { + adoc.readUnlock(); + } + + if (!tokens.isEmpty()) { + tokens.seekTo(caretOffset); + Token queryToken; + + if (tokens.hasNext()) { + queryToken = tokens.next().get(); + } else if (tokens.hasPrevious()) { + queryToken = tokens.previous().get(); + } else { + return; + } + + int queryTokenOffset = queryToken.getStartIndex(); + + //check for context where we have inline css code or offseted blade tags + if (queryTokenOffset > caretOffset) { + int correction = typedText.equals("!") //NOI18N + || typedText.equals("}") ? 0 : 1; //NOI18N + tokens.seekTo(caretOffset - correction); + queryToken = tokens.next().get(); + queryTokenOffset = queryToken.getStartIndex(); + } + + String queryText = queryToken.getText(); + int textLength = queryText.length(); + int endOffset = queryTokenOffset + textLength; + + if (endOffset < caretOffset){ + //out of range + return; + } + + switch (queryToken.getType()) { + case BladeHtmlAntlrLexer.TAG_PART: { + tokens.seekTo(queryToken.getStartIndex() - 1); + if (tokens.hasNext()){ + Token previousToken = tokens.next().get(); + if (previousToken.getType() == BladeHtmlAntlrLexer.RAW_TAG_OPEN && queryText.equals("!")){ //NOI18N + addBladeTagCompletionItem(BladeTagsUtils.RAW_TAG_CLOSE, caretOffset, resultSet); //NOI18N + } + } + break; + } + case BladeHtmlAntlrLexer.HTML_COMPONENT_OPEN_TAG: { + String identifier = ComponentsSupport.tag2ClassName(queryToken.getText()); + completeComponents(identifier, fo, caretOffset, resultSet); + break; + } + case BladeHtmlAntlrLexer.COMPONENT_ATTRIBUTE: { + Set stopTokens = new HashSet<>(); + stopTokens.add(BladeHtmlAntlrLexer.HTML_COMPONENT_OPEN_TAG); + stopTokens.add(BladeHtmlAntlrLexer.GT); + String attributeIdentifier = queryText.startsWith(":") ? queryText.substring(1) : queryText; //NOI18N + Token componentToken = BladeHtmlAntlrUtils.findBackwardWithStop(tokens, BladeHtmlAntlrLexer.HTML_COMPONENT_OPEN_TAG, stopTokens); + if (componentToken != null && componentToken.getType() == BladeHtmlAntlrLexer.HTML_COMPONENT_OPEN_TAG) { + ComponentsQueryService componentComplervice = new ComponentsQueryService(); + String identifier = ComponentsSupport.tag2ClassName(componentToken.getText()); + Collection indexedReferences = componentComplervice.findComponentClass(identifier, fo); + Project projectOwner = FileSystemUtils.getProjectOwner(doc); + ComponentsSupport componentSupport = ComponentsSupport.getInstance(projectOwner); + + if (componentSupport == null){ + break; + } + + for (PhpIndexResult indexReference : indexedReferences) { + ComponentModel componentModel = componentSupport.findComponentClass(indexReference.declarationFile); + if (componentModel == null){ + continue; + } + for (FormalParameter parameter : componentModel.getConstructorProperties()){ + String parameterName = parameter.getParameterName().toString().substring(1); + if (parameterName.startsWith(attributeIdentifier)){ + addSimplAttributeItem(attributeIdentifier, parameterName, caretOffset, resultSet); + } + } + } + } + break; + } + } + + } + } finally { + long time = System.currentTimeMillis() - startTime; + if (time > 2000) { + LOGGER.log(Level.INFO, "Slow completion time detected. {0}ms", time); //NOI18N + } + resultSet.finish(); + } + } + } + + private void completeComponents(String prefixIdentifier, FileObject fo, + int caretOffset, CompletionResultSet resultSet) { + + int insertOffset = caretOffset - prefixIdentifier.length(); + ComponentsQueryService componentComplervice = new ComponentsQueryService(); + Collection indexedReferences = componentComplervice.queryComponents(prefixIdentifier, fo); + + for (PhpIndexResult indexReference : indexedReferences) { + addComponentIdCompletionItem(indexReference, + insertOffset, resultSet); + } + + } + + private void addSimplAttributeItem(String prefix, String attributeName, int caretOffset, CompletionResultSet resultSet) { + int insertOffset = caretOffset - prefix.length(); + CompletionItem item = CompletionUtilities.newCompletionItemBuilder(attributeName) + .iconResource(XML_ATTRIBUTE_ICON) + .startOffset(insertOffset) + .leftHtmlText(attributeName) + .rightHtmlText(" component attribute") //NOI18N + .sortPriority(1) + .build(); + resultSet.addItem(item); + } + + private void addComponentIdCompletionItem(PhpIndexResult indexReference, + int caretOffset, CompletionResultSet resultSet) { + + String tagName = StringUtils.toKebabCase(indexReference.name); + CompletionItem item = CompletionUtilities.newCompletionItemBuilder(tagName) + .iconResource(CUSTOM_HTML_ICON) + .startOffset(caretOffset) + .leftHtmlText(tagName) + .rightHtmlText(indexReference.namespace) + .sortPriority(1) + .build(); + resultSet.addItem(item); + } + + + private void addBladeTagCompletionItem(String tag, + int caretOffset, CompletionResultSet resultSet) { + + String tagName = StringUtils.toKebabCase(tag); + CompletionItem item = CompletionUtilities.newCompletionItemBuilder(tagName) + .iconResource(CUSTOM_HTML_ICON) + .startOffset(caretOffset) + .leftHtmlText(tagName) + .rightHtmlText(tag) + .sortPriority(1) + .build(); + resultSet.addItem(item); + } + +} diff --git a/php/php.blade/src/org/netbeans/modules/php/blade/editor/components/ComponentModel.java b/php/php.blade/src/org/netbeans/modules/php/blade/editor/components/ComponentModel.java new file mode 100644 index 000000000000..1576220ac0b1 --- /dev/null +++ b/php/php.blade/src/org/netbeans/modules/php/blade/editor/components/ComponentModel.java @@ -0,0 +1,64 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.php.blade.editor.components; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; +import org.netbeans.modules.php.editor.parser.astnodes.FormalParameter; +import org.openide.filesystems.FileObject; + +/** + * Basic model class for to store information for a laravel component php class + * + * @author bhaidu + */ +public class ComponentModel { + + private final FileObject file; + private boolean isValid = false; + private final Set constructorProperties = new HashSet<>(); + + private final String[] componentParentClassNames = new String[]{"Component", "BladeComponent"}; // NOI18N + private final Set componentParentClassNamesSet = new HashSet<>(Arrays.asList(componentParentClassNames)); + + public ComponentModel(FileObject file) { + this.file = file; + } + + public boolean isValid() { + return isValid; + } + + public void checkClassValidity(String className) { + isValid = componentParentClassNamesSet.contains(className); + } + + public void addConstructorProperty(FormalParameter property) { + constructorProperties.add(property); + } + + public Set getConstructorProperties() { + return constructorProperties; + } + + public FileObject getFile() { + return file; + } +} diff --git a/php/php.blade/src/org/netbeans/modules/php/blade/editor/components/ComponentsQueryService.java b/php/php.blade/src/org/netbeans/modules/php/blade/editor/components/ComponentsQueryService.java new file mode 100644 index 000000000000..84c09294764e --- /dev/null +++ b/php/php.blade/src/org/netbeans/modules/php/blade/editor/components/ComponentsQueryService.java @@ -0,0 +1,153 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.php.blade.editor.components; + +import org.netbeans.modules.php.blade.editor.components.annotation.Namespace; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Map; +import org.netbeans.api.annotations.common.CheckForNull; +import org.netbeans.api.project.Project; +import org.netbeans.modules.csl.api.OffsetRange; +import org.netbeans.modules.php.blade.editor.BladeLanguage; +import org.netbeans.modules.php.blade.editor.components.plugins.LivewireComponentResource; +import org.netbeans.modules.php.blade.editor.indexing.PhpIndexResult; +import org.netbeans.modules.php.blade.editor.indexing.PhpIndexUtils; +import org.netbeans.modules.php.blade.project.ComponentsSupport; +import org.netbeans.modules.php.blade.project.ProjectUtils; +import org.netbeans.modules.php.blade.syntax.StringUtils; +import org.openide.filesystems.FileObject; + +/** + * Service class used to search, from index, info about a php component class using identified project related filters like : + * - namespace + * - file path + * + * + * + * @author bhaidu + */ +public class ComponentsQueryService { + + + public Collection queryComponents(String prefix, FileObject fo) { + Collection results = new ArrayList<>(); + Project project = ProjectUtils.getMainOwner(fo); + + if (project == null) { + return results; + } + + ComponentsSupport componentSupport = ComponentsSupport.getInstance(project); + + if (!componentSupport.isScanned()) { + componentSupport.scanForInstalledComponents(); + componentSupport.scanCustomComponentsFolders(); + } + + for (Map.Entry namespace : componentSupport.getInstalledComponentNamespace().entrySet()) { + results.addAll(PhpIndexUtils.queryNamespaceClassesName(fo, prefix, namespace.getValue().path())); + } + + for (Map.Entry componentEntry : componentSupport.getComponentClassCollection().entrySet()) { + FileObject parentDir = componentEntry.getKey().getParent(); + if (componentSupport.getInstalledComponentNamespace().containsKey(parentDir)) { + continue; + } + String className = componentEntry.getKey().getName(); + if (className.toLowerCase().startsWith(prefix)) { + results.add(new PhpIndexResult(className, componentEntry.getKey(), PhpIndexResult.Type.CLASS, new OffsetRange(0, 1))); + } + } + + return results; + } + + public Collection findComponentClass(String prefixClassName, FileObject fo) { + Collection results = new ArrayList<>(); + Project project = ProjectUtils.getMainOwner(fo); + + if (project == null) { + return results; + } + + ComponentsSupport componentSupport = ComponentsSupport.getInstance(project); + + if (!componentSupport.isScanned()) { + componentSupport.scanForInstalledComponents(); + componentSupport.scanCustomComponentsFolders(); + } else if (componentSupport.getComponentClassCollection().isEmpty()) { + componentSupport.scanCustomComponentsFolders(); + } + + for (Map.Entry namespace : componentSupport.getInstalledComponentNamespace().entrySet()) { + results.addAll(PhpIndexUtils.queryExactNamespaceClasses(prefixClassName, namespace.getValue().path(), fo)); + } + + if (prefixClassName.contains(StringUtils.DOT)) { + //NOT a complete flow, but it should cover the necessities + String classPathParts[] = prefixClassName.split(StringUtils.ESCAPED_DOT); + String prefixClassPathName = classPathParts[classPathParts.length - 1]; + for (Map.Entry componentEntry : componentSupport.getComponentClassCollection().entrySet()) { + String className = componentEntry.getKey().getName().toLowerCase(); + if (className.equals(prefixClassPathName)) { + results.add(new PhpIndexResult(className, componentEntry.getKey(), PhpIndexResult.Type.CLASS, new OffsetRange(0, 1))); + } + } + } else { + for (Map.Entry componentEntry : componentSupport.getComponentClassCollection().entrySet()) { + FileObject parentDir = componentEntry.getKey().getParent(); + if (componentSupport.getInstalledComponentNamespace().containsKey(parentDir)) { + continue; + } + String className = componentEntry.getKey().getName(); + if (className.equals(prefixClassName)) { + results.add(new PhpIndexResult(className, componentEntry.getKey(), PhpIndexResult.Type.CLASS, new OffsetRange(0, 1))); + } + } + } + + return results; + } + + @CheckForNull + public FileObject getComponentResourceFile(String componentId, String classQualifiedName, FileObject sourceFo) { + if (classQualifiedName.toLowerCase().contains(LivewireComponentResource.LIVEWIRE_NAME)) { + return getLivewireComponentResourceFile(componentId, sourceFo); + } + + return null; + } + + @CheckForNull + public FileObject getLivewireComponentResourceFile(String componentId, FileObject sourceFo) { + Project project = ProjectUtils.getMainOwner(sourceFo); + if (project == null) { + return null; + } + + FileObject componentResource = project.getProjectDirectory().getFileObject(LivewireComponentResource.RESOURCE_PATH + componentId + BladeLanguage.FILE_EXTENSION_WITH_DOT); // NOI18N + + if (componentResource != null && componentResource.isValid()) { + return componentResource; + } + + return null; + } +} diff --git a/php/php.blade/src/org/netbeans/modules/php/blade/editor/components/annotation/Attribute.java b/php/php.blade/src/org/netbeans/modules/php/blade/editor/components/annotation/Attribute.java new file mode 100644 index 000000000000..a8cde3f03713 --- /dev/null +++ b/php/php.blade/src/org/netbeans/modules/php/blade/editor/components/annotation/Attribute.java @@ -0,0 +1,27 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.php.blade.editor.components.annotation; + +/** + * + * @author bhaidu + */ +public @interface Attribute { + String name(); +} diff --git a/php/php.blade/src/org/netbeans/modules/php/blade/editor/components/annotation/AttributeRegister.java b/php/php.blade/src/org/netbeans/modules/php/blade/editor/components/annotation/AttributeRegister.java new file mode 100644 index 000000000000..9090bb74be54 --- /dev/null +++ b/php/php.blade/src/org/netbeans/modules/php/blade/editor/components/annotation/AttributeRegister.java @@ -0,0 +1,34 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.php.blade.editor.components.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * + * @author bhaidu + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(value = {ElementType.TYPE, ElementType.FIELD, ElementType.METHOD, ElementType.PACKAGE}) +public @interface AttributeRegister { + public Attribute[] value(); +} diff --git a/php/php.blade/src/org/netbeans/modules/php/blade/editor/components/annotation/Namespace.java b/php/php.blade/src/org/netbeans/modules/php/blade/editor/components/annotation/Namespace.java new file mode 100644 index 000000000000..4fede8568c49 --- /dev/null +++ b/php/php.blade/src/org/netbeans/modules/php/blade/editor/components/annotation/Namespace.java @@ -0,0 +1,32 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.php.blade.editor.components.annotation; + +/** + * + * @author bhaidu + */ +public @interface Namespace { + String path(); + String relativeFilePath() default ""; + String packageName() default "blade"; // NOI18N + //inside App folder + boolean fromApp() default false; + boolean from_vendor() default true; +} diff --git a/php/php.blade/src/org/netbeans/modules/php/blade/editor/components/annotation/NamespaceRegister.java b/php/php.blade/src/org/netbeans/modules/php/blade/editor/components/annotation/NamespaceRegister.java new file mode 100644 index 000000000000..2cc4cf16cfbc --- /dev/null +++ b/php/php.blade/src/org/netbeans/modules/php/blade/editor/components/annotation/NamespaceRegister.java @@ -0,0 +1,34 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.php.blade.editor.components.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * + * @author bhaidu + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(value = {ElementType.TYPE, ElementType.FIELD, ElementType.METHOD, ElementType.PACKAGE}) +public @interface NamespaceRegister { + public Namespace[] value(); +} diff --git a/php/php.blade/src/org/netbeans/modules/php/blade/editor/components/plugins/LivewireComponentResource.java b/php/php.blade/src/org/netbeans/modules/php/blade/editor/components/plugins/LivewireComponentResource.java new file mode 100644 index 000000000000..c4660168a962 --- /dev/null +++ b/php/php.blade/src/org/netbeans/modules/php/blade/editor/components/plugins/LivewireComponentResource.java @@ -0,0 +1,28 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.php.blade.editor.components.plugins; + +/** + * + * @author bogdan + */ +public class LivewireComponentResource { + public static final String LIVEWIRE_NAME = "livewire"; // NOI18N + public static final String RESOURCE_PATH = "resources/views/livewire/"; // NOI18N +} diff --git a/php/php.blade/src/org/netbeans/modules/php/blade/editor/declaration/BladeDeclarationFinder.java b/php/php.blade/src/org/netbeans/modules/php/blade/editor/declaration/BladeDeclarationFinder.java new file mode 100644 index 000000000000..419b68f1bd81 --- /dev/null +++ b/php/php.blade/src/org/netbeans/modules/php/blade/editor/declaration/BladeDeclarationFinder.java @@ -0,0 +1,579 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.php.blade.editor.declaration; + +import java.util.Collection; +import java.util.List; +import javax.swing.text.AbstractDocument; +import javax.swing.text.BadLocationException; +import javax.swing.text.Document; +import org.netbeans.api.html.lexer.HTMLTokenId; +import org.netbeans.api.lexer.Token; +import org.netbeans.api.lexer.TokenHierarchy; +import org.netbeans.api.lexer.TokenSequence; +import org.netbeans.api.project.Project; +import org.netbeans.modules.csl.api.DeclarationFinder; +import org.netbeans.modules.csl.api.OffsetRange; +import org.netbeans.modules.csl.spi.ParserResult; +import org.netbeans.modules.csl.api.ElementHandle; +import org.netbeans.modules.csl.api.HtmlFormatter; +import org.netbeans.modules.php.blade.csl.elements.ElementType; +import org.netbeans.modules.php.blade.csl.elements.NamedElement; +import org.netbeans.modules.php.blade.csl.elements.PathElement; +import org.netbeans.modules.php.blade.csl.elements.PhpFunctionElement; +import org.netbeans.modules.php.blade.editor.components.ComponentsQueryService; +import org.netbeans.modules.php.blade.editor.directives.CustomDirectives; +import org.netbeans.modules.php.blade.editor.directives.CustomDirectives.CustomDirective; +import org.netbeans.modules.php.blade.editor.indexing.BladeIndex; +import org.netbeans.modules.php.blade.editor.indexing.PhpIndexFunctionResult; +import org.netbeans.modules.php.blade.editor.indexing.PhpIndexResult; +import org.netbeans.modules.php.blade.editor.indexing.PhpIndexUtils; +import org.netbeans.modules.php.blade.editor.indexing.QueryUtils; +import org.netbeans.modules.php.blade.editor.lexer.BladeLexerUtils; +import org.netbeans.modules.php.blade.editor.lexer.BladeTokenId; +import static org.netbeans.modules.php.blade.editor.lexer.BladeTokenId.BLADE_CUSTOM_DIRECTIVE; +import static org.netbeans.modules.php.blade.editor.lexer.BladeTokenId.BLADE_PAREN; +import static org.netbeans.modules.php.blade.editor.lexer.BladeTokenId.HTML; +import static org.netbeans.modules.php.blade.editor.lexer.BladeTokenId.PHP_BLADE_ECHO_EXPR; +import static org.netbeans.modules.php.blade.editor.lexer.BladeTokenId.PHP_BLADE_EXPRESSION; +import static org.netbeans.modules.php.blade.editor.lexer.BladeTokenId.PHP_BLADE_INLINE_CODE; +import org.netbeans.modules.php.blade.editor.parser.BladeCustomDirectiveOccurences.CustomDirectiveOccurence; +import org.netbeans.modules.php.blade.editor.parser.BladeParserResult; +import org.netbeans.modules.php.blade.editor.parser.BladeParserResult.BladeStringReference; +import org.netbeans.modules.php.blade.editor.parser.BladeParserResult.Reference; +import org.netbeans.modules.php.blade.editor.path.BladePathUtils; +import org.netbeans.modules.php.blade.project.ComponentsSupport; +import static org.netbeans.modules.php.blade.project.ComponentsSupport.COMPONENT_TAG_NAME_PREFIX; +import org.netbeans.modules.php.blade.syntax.StringUtils; +import static org.netbeans.modules.php.blade.syntax.antlr4.php.BladePhpAntlrParser.IDENTIFIER; +import org.netbeans.modules.php.blade.syntax.antlr4.php.BladePhpAntlrUtils; +import org.netbeans.modules.php.blade.editor.parser.BladePhpSnippetParser.PhpReference; +import org.openide.filesystems.FileObject; +import static org.netbeans.modules.php.blade.syntax.antlr4.v10.BladeAntlrLexer.*; +import org.openide.filesystems.FileUtil; +import org.openide.util.Exceptions; +import static org.netbeans.modules.php.blade.editor.parser.BladePhpSnippetParser.PhpReferenceType.PHP_FUNCTION; +import org.netbeans.modules.php.blade.syntax.antlr4.utils.BladeAntlrLexerUtils; +import org.netbeans.modules.php.editor.lexer.PHPTokenId; +import org.netbeans.spi.project.ui.support.ProjectConvertors; + +/** + * declaration finder for : + * - include paths + * - section related yields + * - stack locations + * - vite assets path + * - custom directive definitions + * - components class origin + * + * + * @author bhaidu + */ +public class BladeDeclarationFinder implements DeclarationFinder { + + @Override + public OffsetRange getReferenceSpan(Document document, int caretOffset) { + OffsetRange offsetRange = OffsetRange.NONE; + AbstractDocument adoc = (AbstractDocument) document; + + try { + adoc.readLock(); + TokenSequence ts = BladeLexerUtils.getTokenSequence(document, caretOffset); + + if (ts == null) { + return offsetRange; + } + + ts.move(caretOffset); + + if (!ts.moveNext() && !ts.movePrevious()) { + return offsetRange; + } + + Token token = ts.token(); + String caretTokenText = token.text().toString(); + BladeTokenId id = token.id(); + int tokenStart = ts.offset(); + + switch (id) { + case BLADE_CUSTOM_DIRECTIVE: { + return new OffsetRange(tokenStart, tokenStart + caretTokenText.length()); + } + case PHP_BLADE_EXPRESSION: { + return getReferenceSpanInsidePhpExpr(document, ts, token, caretOffset); + } + case PHP_BLADE_ECHO_EXPR: + case PHP_BLADE_INLINE_CODE: { + return getPhpReferenceSpan(caretTokenText, caretOffset, tokenStart); + } + case PHP_INLINE: { + TokenSequence tsPhp = BladeLexerUtils.getPhpTokenSequence(document, caretOffset); + if (tsPhp == null) { + return offsetRange; + } + tsPhp.move(caretOffset); + + if (!tsPhp.moveNext() && !tsPhp.movePrevious()) { + return offsetRange; + } + + Token phpToken = tsPhp.token(); + PHPTokenId phpTokenId = phpToken.id(); + if (phpTokenId.equals(PHPTokenId.PHP_STRING)) { + return new OffsetRange(tsPhp.offset(), tsPhp.offset() + phpToken.length()); + } + break; + } + case HTML: { + TokenHierarchy th = TokenHierarchy.get(document); + Token htmlToken = BladeLexerUtils.getHtmlToken(th, caretOffset); + + if (htmlToken == null) { + return offsetRange; + } + + HTMLTokenId htmlTokenId = htmlToken.id(); + int tokenOffset = htmlToken.offset(th); + + if (htmlTokenId.equals(HTMLTokenId.TAG_OPEN)) { + String tag = htmlToken.text().toString(); + if (tag.startsWith(COMPONENT_TAG_NAME_PREFIX)) { // NOI18N + return new OffsetRange(tokenOffset, tokenOffset + htmlToken.length()); + } + } + + break; + } + + } + } finally { + adoc.readUnlock(); + } + + return offsetRange; + } + + public OffsetRange getReferenceSpanInsidePhpExpr(Document document, + TokenSequence ts, + Token token, int caretOffset) { + + OffsetRange offsetRange = OffsetRange.NONE; + int phpExprStart = ts.offset(); + ts.movePrevious(); + + boolean prevTokenIsParenthesis = ts.token().id().equals(BLADE_PAREN); + + if (prevTokenIsParenthesis && !ts.movePrevious()) { + //move before the parenthesis + return offsetRange; + } + + Token prevToken = ts.token(); + int start = ts.offset(); + int snippetLength = prevToken.length() + token.length() + 2; + int end = ts.offset() + snippetLength; + + boolean isBetween = start <= caretOffset && caretOffset <= end; + + if (!isBetween) { + return offsetRange; + } + + if (snippetLength > document.getLength()) { + return offsetRange; + } + + try { + String snippet = document.getText(start, snippetLength); + int parenPos = snippet.indexOf("("); // NOI18N + String directive = snippet.startsWith("@") && parenPos != -1 ? snippet.substring(0, parenPos) : null; // NOI18N + int referencedOffset = caretOffset - start; + + if (directive != null) {//we can filter for identifiable directives here + org.antlr.v4.runtime.Token targetetToken = BladeAntlrLexerUtils.getToken(snippet, referencedOffset); + if (targetetToken != null && targetetToken.getType() == IDENTIFIABLE_STRING) { + offsetRange = new OffsetRange(targetetToken.getStartIndex() + start, start + targetetToken.getStopIndex() + 1); + return offsetRange; + } + } + //php context + return getPhpReferenceSpan(token.text().toString(), caretOffset, phpExprStart); + } catch (BadLocationException ex) { + Exceptions.printStackTrace(ex); + } + return offsetRange; + } + + @Override + public DeclarationLocation findDeclaration(ParserResult info, int caretOffset) { + DeclarationLocation location = DeclarationLocation.NONE; + final TokenHierarchy th = info.getSnapshot().getTokenHierarchy(); + + if (th == null) { + return location; + } + + TokenSequence ts = BladeLexerUtils.getBladeTokenSequenceDoc(th, caretOffset); + + if (ts == null) { + return location; + } + + ts.move(caretOffset); + + if (!ts.moveNext() && !ts.movePrevious()) { + return location; + } + + Token token = ts.token(); + BladeTokenId id = token.id(); + + BladeParserResult parserResult = (BladeParserResult) info; + FileObject currentFile = parserResult.getFileObject(); + + switch (id) { + case BLADE_CUSTOM_DIRECTIVE: { + return findCustomDirectiveDeclaration(parserResult, caretOffset, currentFile, location); + } + case HTML: { + return findComponentClassDeclaration(th, caretOffset, currentFile, location); + } + } + //we can have string or php reference + BladeStringReference bladeReference = parserResult.getBladeReferenceIdsCollection().findOccuredRefrence(caretOffset); + OffsetRange caretRange; + + if (bladeReference != null) { + String referenceIdentifier = bladeReference.identifier; + + switch (bladeReference.antlrTokentype) { + case D_SECTION: + case D_HAS_SECTION: + case D_SECTION_MISSING: { + String yieldId = referenceIdentifier; + List yields = QueryUtils.findYieldReferences(yieldId, currentFile); + if (yields == null) { + return DeclarationLocation.NONE; + } + + for (BladeIndex.IndexedReference yieldReference : yields) { + location = buildDeclarationLocationFromIndex(location, yieldReference, ElementType.YIELD_ID); + } + + return location; + } + case D_EXTENDS: + case D_INCLUDE: + case D_INCLUDE_IF: + case D_INCLUDE_WHEN: + case D_INCLUDE_UNLESS: + case D_INCLUDE_FIRST: + case D_EACH: { + String viewPath = referenceIdentifier; + List includedFiles = BladePathUtils.findFileObjectsForBladeViewPath(currentFile, viewPath); + + if (includedFiles.isEmpty()) { + return DeclarationLocation.NONE; + } + + for (FileObject includedFile : includedFiles) { + PathElement elHandle = new PathElement(referenceIdentifier, includedFile); + DeclarationLocation dln = new DeclarationFinder.DeclarationLocation(includedFile, 0, elHandle); + if (location.equals(DeclarationLocation.NONE)) { + location = dln; + } + location.addAlternative(new AlternativeLocationImpl(dln)); + } + return location; + } + case D_PUSH: + case D_PUSH_IF: + case D_PREPEND: { + String stackId = referenceIdentifier; + List stacks = QueryUtils.findStacksReferences(stackId, currentFile); + + if (stacks == null) { + return DeclarationLocation.NONE; + } + + for (BladeIndex.IndexedReference stackReference : stacks) { + String stackReferenceId = stackReference.getReference().identifier; + NamedElement yieldIdHandle = new NamedElement(stackReferenceId, stackReference.getOriginFile(), ElementType.STACK_ID); + int startOccurence = stackReference.getReference().defOffset.getStart(); + DeclarationLocation dlstack = new DeclarationFinder.DeclarationLocation(stackReference.getOriginFile(), startOccurence, yieldIdHandle); + if (location.equals(DeclarationLocation.NONE)) { + location = dlstack; + } + location.addAlternative(new AlternativeLocationImpl(dlstack)); + } + + return location; + } + case D_VITE: { + String vitePath = referenceIdentifier; + Project projectOwner = ProjectConvertors.getNonConvertorOwner(currentFile); + + if (projectOwner == null || projectOwner.getProjectDirectory() == null) { + return location; + } + + FileObject assetFile = projectOwner.getProjectDirectory().getFileObject(vitePath); + + if (assetFile != null) { + NamedElement resultHandle = new NamedElement(referenceIdentifier, assetFile, ElementType.ASSET_FILE); + DeclarationLocation constantLocation = new DeclarationFinder.DeclarationLocation(assetFile, 0, resultHandle); + if (location.equals(DeclarationLocation.NONE)) { + location = constantLocation; + } + location.addAlternative(new AlternativeLocationImpl(constantLocation)); + return location; + } + } + } + } else if ((caretRange = parserResult.getBladePhpExpressionOccurences().findPhpExpressionLocation(caretOffset)) != null) { + int referenceOffset = caretOffset - caretRange.getStart(); + PhpElementsDeclarationService phpDeclService = new PhpElementsDeclarationService(); + PhpReference phpRef = phpDeclService.findReferenceAtCaret(info, caretRange, referenceOffset, currentFile); + + if (phpRef == null) { + return location; + } + + switch (phpRef.type) { + case PHP_FUNCTION: { + Collection functionResults = PhpIndexUtils.queryExactFunctions(currentFile, phpRef.identifier); + return phpDeclService.buildFunctionDeclLocation(phpRef, functionResults); + } + case PHP_CLASS: { + Collection results; + if (phpRef.namespace != null) { + results = PhpIndexUtils.queryExactNamespaceClasses(phpRef.identifier, phpRef.namespace, currentFile); + } else { + results = PhpIndexUtils.queryExactClass(phpRef.identifier, currentFile); + } + + if (results != null && !results.isEmpty()) { + return phpDeclService.buildDeclLocation(phpRef.identifier, ElementType.PHP_CLASS, results); + } + + return location; + } + case PHP_METHOD: { + String queryNamespace = phpRef.namespace; + + if (phpRef.ownerClass == null){ + return location; + } + + Collection indexMethodResults = PhpIndexUtils.queryExactClassMethods(currentFile, + phpRef.identifier, phpRef.ownerClass.identifier, queryNamespace); + + for (PhpIndexFunctionResult indexResult : indexMethodResults) { + PhpFunctionElement resultHandle = new PhpFunctionElement( + phpRef.identifier, + indexResult.declarationFile, + ElementType.PHP_FUNCTION, + indexResult.getClassNamespace(), + indexResult.getParams() + ); + DeclarationLocation functionLocation = new DeclarationFinder.DeclarationLocation(indexResult.declarationFile, indexResult.getStartOffset(), resultHandle); + if (location.equals(DeclarationLocation.NONE)) { + location = functionLocation; + } + location.addAlternative(new AlternativeLocationImpl(functionLocation)); + } + return location; + } + case PHP_CLASS_CONSTANT: { + Collection results = null; + if (phpRef.ownerClass != null) { + results = PhpIndexUtils.queryExactClassConstants(phpRef.identifier, phpRef.ownerClass.identifier, currentFile); + } + if (results != null && !results.isEmpty()) { + return phpDeclService.buildDeclLocation(phpRef.identifier, ElementType.PHP_CONSTANT, results); + } + + return location; + } + } + } + + return DeclarationLocation.NONE; + } + + private DeclarationLocation findCustomDirectiveDeclaration( + BladeParserResult parserResult, int caretOffset, FileObject currentFile, + DeclarationLocation location + ) { + Project projectOwner = ProjectConvertors.getNonConvertorOwner(currentFile); + if (projectOwner == null) { + return location; + } + + CustomDirectiveOccurence customDirectiveOccurence = parserResult.getBladeCustomDirectiveOccurences().findCustomDirectiveOccurence(caretOffset); + + if (customDirectiveOccurence == null) { + return location; + } + + CustomDirectives.getInstance(projectOwner).filterAction(new CustomDirectives.FilterCallbackDeclaration(location) { + @Override + public void filterDirectiveName(CustomDirective directive, FileObject file) { + if (directive.getName().equals(customDirectiveOccurence.directiveName)) { + NamedElement customDirectiveHandle = new NamedElement(customDirectiveOccurence.directiveName, file, ElementType.CUSTOM_DIRECTIVE); + DeclarationFinder.DeclarationLocation newLoc = new DeclarationFinder.DeclarationLocation(file, directive.getOffset(), customDirectiveHandle); + this.getLocation().addAlternative(new AlternativeLocationImpl(newLoc)); + } + } + }); + + if (!location.getAlternativeLocations().isEmpty()) { + for (AlternativeLocation loc : location.getAlternativeLocations()) { + location = loc.getLocation(); + } + } + return location; + } + + private DeclarationLocation findComponentClassDeclaration( + TokenHierarchy th, int caretOffset, FileObject currentFile, DeclarationLocation location) { + Project projectOwner = ProjectConvertors.getNonConvertorOwner(currentFile); + if (projectOwner == null) { + return location; + } + + Token htmlToken = BladeLexerUtils.getHtmlToken(th, caretOffset); + + if (htmlToken == null) { + return location; + } + + HTMLTokenId htmlTokenId = htmlToken.id(); + + if (htmlTokenId.equals(HTMLTokenId.TAG_OPEN)) { + String tag = htmlToken.text().toString(); + if (!tag.startsWith(COMPONENT_TAG_NAME_PREFIX)) { + return location; + } + ComponentsQueryService componentComplervice = new ComponentsQueryService(); + String className = StringUtils.kebabToCamel(tag.substring(COMPONENT_TAG_NAME_PREFIX.length())); + + Collection indexedReferences = componentComplervice.findComponentClass(className, currentFile); + ComponentsSupport componentSupport = ComponentsSupport.getInstance(projectOwner); + + if (componentSupport == null) { + return location; + } + + for (PhpIndexResult indexReference : indexedReferences) { + NamedElement resultHandle = new NamedElement(className, indexReference.declarationFile, ElementType.LARAVEL_COMPONENT); + DeclarationLocation constantLocation = new DeclarationFinder.DeclarationLocation(indexReference.declarationFile, indexReference.getStartOffset(), resultHandle); + if (location.equals(DeclarationLocation.NONE)) { + location = constantLocation; + } + location.addAlternative(new AlternativeLocationImpl(constantLocation)); + + if (!location.equals(DeclarationLocation.NONE)) { + FileObject resource = componentComplervice.getComponentResourceFile(tag, indexReference.name, currentFile); + if (resource != null) { + PathElement resourceHandle = new PathElement(tag, resource); + DeclarationLocation resourceLocation = new DeclarationFinder.DeclarationLocation(resource, indexReference.getStartOffset(), resourceHandle); + location.addAlternative(new AlternativeLocationImpl(resourceLocation)); + } + } + } + } + return location; + } + + public static class AlternativeLocationImpl implements AlternativeLocation { + + private final DeclarationLocation location; + + public AlternativeLocationImpl(DeclarationLocation location) { + this.location = location; + } + + @Override + public ElementHandle getElement() { + return getLocation().getElement(); + } + + @Override + public String getDisplayHtml(HtmlFormatter formatter) { + ElementHandle el = getLocation().getElement(); + if (el != null) { + formatter.appendText(el.getName()); + if (el.getFileObject() != null) { + formatter.appendText(" in "); // NOI18N + formatter.appendText(FileUtil.getFileDisplayName(el.getFileObject())); + } + return formatter.getText(); + } + return getLocation().toString(); + } + + @Override + public DeclarationFinder.DeclarationLocation getLocation() { + return location; + } + + @Override + public int compareTo(DeclarationFinder.AlternativeLocation o) { + return 0; + } + + } + + //php context + public OffsetRange getPhpReferenceSpan(String phpExpr, int caretOffset, int phpExprStart) { + OffsetRange offsetRange = OffsetRange.NONE; + + int referencedOffset = caretOffset - phpExprStart; + org.antlr.v4.runtime.Token targetetToken = BladePhpAntlrUtils.getToken(phpExpr, referencedOffset); + + if (targetetToken == null) { + return offsetRange; + } + + if (targetetToken.getType() == IDENTIFIER) { + offsetRange = new OffsetRange(targetetToken.getStartIndex() + phpExprStart, phpExprStart + targetetToken.getStopIndex() + 1); + return offsetRange; + } + + return offsetRange; + } + + private DeclarationLocation buildDeclarationLocationFromIndex(DeclarationLocation location, + BladeIndex.IndexedReference indexedReference, ElementType type) { + Reference reference = indexedReference.getReference(); + String referenceId = reference.identifier; + FileObject originFile = indexedReference.getOriginFile(); + NamedElement referenceHandle = new NamedElement(referenceId, originFile, type); + int startOccurence = reference.defOffset.getStart(); + DeclarationLocation declItem = new DeclarationFinder.DeclarationLocation(originFile, startOccurence, referenceHandle); + + if (location.equals(DeclarationLocation.NONE)) { + location = declItem; + } + + location.addAlternative(new AlternativeLocationImpl(declItem)); + return location; + } +} diff --git a/php/php.blade/src/org/netbeans/modules/php/blade/editor/declaration/ComponentDeclarationService.java b/php/php.blade/src/org/netbeans/modules/php/blade/editor/declaration/ComponentDeclarationService.java new file mode 100644 index 000000000000..422ae00901fa --- /dev/null +++ b/php/php.blade/src/org/netbeans/modules/php/blade/editor/declaration/ComponentDeclarationService.java @@ -0,0 +1,86 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.php.blade.editor.declaration; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Map; +import org.netbeans.api.annotations.common.CheckForNull; +import org.netbeans.api.project.Project; +import org.netbeans.modules.php.blade.editor.BladeLanguage; +import org.netbeans.modules.php.blade.editor.components.annotation.Namespace; +import org.netbeans.modules.php.blade.editor.components.plugins.LivewireComponentResource; +import org.netbeans.modules.php.blade.editor.indexing.PhpIndexResult; +import org.netbeans.modules.php.blade.editor.indexing.PhpIndexUtils; +import org.netbeans.modules.php.blade.project.ComponentsSupport; +import org.netbeans.modules.php.blade.project.ProjectUtils; +import org.openide.filesystems.FileObject; + +/** + * + * @author bogdan + */ +public class ComponentDeclarationService { + + public Collection queryComponents(String prefix, FileObject fo) { + Collection results = new ArrayList<>(); + Project project = ProjectUtils.getMainOwner(fo); + + if (project == null) { + return results; + } + + ComponentsSupport componentSupport = ComponentsSupport.getInstance(project); + + if (!componentSupport.isScanned()){ + componentSupport.scanForInstalledComponents(); + } + + for (Map.Entry namespace : componentSupport.getInstalledComponentNamespace().entrySet()) { + results.addAll(PhpIndexUtils.queryComponentClass(prefix, namespace.getValue().path(), fo)); + } + + return results; + } + + @CheckForNull + public FileObject getComponentResourceFile(String componentId, String classQualifiedName, FileObject sourceFo) { + if (classQualifiedName.toLowerCase().contains(LivewireComponentResource.LIVEWIRE_NAME)){ + return getLivewireComponentResourceFile(componentId, sourceFo); + } + + return null; + } + + @CheckForNull + public FileObject getLivewireComponentResourceFile(String componentId, FileObject sourceFo) { + Project project = ProjectUtils.getMainOwner(sourceFo); + if (project == null) { + return null; + } + + FileObject componentResource = project.getProjectDirectory().getFileObject("resources/views/livewire/" + componentId + "." + BladeLanguage.FILE_EXTENSION); // NOI18N + + if (componentResource != null && componentResource.isValid()){ + return componentResource; + } + + return null; + } +} diff --git a/php/php.blade/src/org/netbeans/modules/php/blade/editor/declaration/PhpElementsDeclarationService.java b/php/php.blade/src/org/netbeans/modules/php/blade/editor/declaration/PhpElementsDeclarationService.java new file mode 100644 index 000000000000..ad1e5e6001a1 --- /dev/null +++ b/php/php.blade/src/org/netbeans/modules/php/blade/editor/declaration/PhpElementsDeclarationService.java @@ -0,0 +1,80 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.php.blade.editor.declaration; + +import java.util.Collection; +import org.netbeans.modules.csl.api.DeclarationFinder; +import org.netbeans.modules.csl.api.DeclarationFinder.DeclarationLocation; +import org.netbeans.modules.csl.api.OffsetRange; +import org.netbeans.modules.csl.spi.ParserResult; +import org.netbeans.modules.php.blade.csl.elements.ElementType; +import org.netbeans.modules.php.blade.csl.elements.NamedElement; +import org.netbeans.modules.php.blade.csl.elements.PhpFunctionElement; +import org.netbeans.modules.php.blade.editor.indexing.PhpIndexFunctionResult; +import org.netbeans.modules.php.blade.editor.indexing.PhpIndexResult; +import org.netbeans.modules.php.blade.editor.parser.BladePhpSnippetParser; +import static org.netbeans.modules.php.blade.editor.parser.BladePhpSnippetParser.PhpReference; +import org.openide.filesystems.FileObject; + +/** + * + * @author bogdan + */ +public class PhpElementsDeclarationService { + + public PhpReference findReferenceAtCaret(ParserResult info, OffsetRange phpExprRange, int referenceOffset, FileObject file) { + CharSequence phpExprSnippet = info.getSnapshot().getText().subSequence(phpExprRange.getStart(), phpExprRange.getEnd()); + BladePhpSnippetParser phpSnippetParser = new BladePhpSnippetParser(phpExprSnippet.toString(), file, referenceOffset); + phpSnippetParser.parse(); + return phpSnippetParser.findIdentifierReference(referenceOffset); + } + + public DeclarationLocation buildFunctionDeclLocation(PhpReference phpRef, + Collection functionResults) { + DeclarationLocation location = DeclarationLocation.NONE; + for (PhpIndexFunctionResult indexResult : functionResults) { + PhpFunctionElement resultHandle = new PhpFunctionElement( + phpRef.identifier, + indexResult.declarationFile, + ElementType.PHP_FUNCTION, + indexResult.getParams() + ); + DeclarationLocation functionLocation = new DeclarationFinder.DeclarationLocation(indexResult.declarationFile, indexResult.getStartOffset(), resultHandle); + if (location.equals(DeclarationLocation.NONE)) { + location = functionLocation; + } + location.addAlternative(new BladeDeclarationFinder.AlternativeLocationImpl(functionLocation)); + } + + return location; + } + + public DeclarationLocation buildDeclLocation(String referenceIdentifier, ElementType type, Collection indexedResults) { + DeclarationLocation location = DeclarationLocation.NONE; + for (PhpIndexResult indexResult : indexedResults) { + NamedElement resultHandle = new NamedElement(referenceIdentifier, indexResult.declarationFile, type); + DeclarationLocation classLocation = new DeclarationFinder.DeclarationLocation(indexResult.declarationFile, indexResult.getStartOffset(), resultHandle); + if (location.equals(DeclarationLocation.NONE)) { + location = classLocation; + } + location.addAlternative(new BladeDeclarationFinder.AlternativeLocationImpl(classLocation)); + } + return location; + } +} diff --git a/php/php.blade/src/org/netbeans/modules/php/blade/editor/declaration/VitePathDeclarationService.java b/php/php.blade/src/org/netbeans/modules/php/blade/editor/declaration/VitePathDeclarationService.java new file mode 100644 index 000000000000..b748f625fa08 --- /dev/null +++ b/php/php.blade/src/org/netbeans/modules/php/blade/editor/declaration/VitePathDeclarationService.java @@ -0,0 +1,38 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.php.blade.editor.declaration; + +import org.openide.filesystems.FileObject; + +/** + * + * @author bogdan + */ +public class VitePathDeclarationService { + + private final FileObject sourceFolder; + + public VitePathDeclarationService(FileObject sourceFolder) { + this.sourceFolder = sourceFolder; + } + + public FileObject findFileObject(String path) { + return sourceFolder.getFileObject(path); + } +} diff --git a/php/php.blade/src/org/netbeans/modules/php/blade/editor/directives/CustomDirectives.java b/php/php.blade/src/org/netbeans/modules/php/blade/editor/directives/CustomDirectives.java new file mode 100644 index 000000000000..21ae1a4a52b8 --- /dev/null +++ b/php/php.blade/src/org/netbeans/modules/php/blade/editor/directives/CustomDirectives.java @@ -0,0 +1,355 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.php.blade.editor.directives; + +import java.io.File; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.WeakHashMap; +import java.util.logging.Level; +import java.util.logging.Logger; +import org.netbeans.api.project.Project; +import org.netbeans.modules.csl.api.DeclarationFinder; +import org.netbeans.modules.parsing.api.ParserManager; +import org.netbeans.modules.parsing.api.ResultIterator; +import org.netbeans.modules.parsing.api.Source; +import org.netbeans.modules.parsing.api.UserTask; +import org.netbeans.modules.parsing.spi.ParseException; +import org.netbeans.modules.php.blade.project.BladeProjectProperties; +import static org.netbeans.modules.php.blade.project.BladeProjectSupport.APP_PROVIDER_RELATIVE_PATH; +import static org.netbeans.modules.php.blade.syntax.BladeDirectivesUtils.DIRECTIVE_ELSE; +import static org.netbeans.modules.php.blade.syntax.BladeDirectivesUtils.END_DIRECTIVE_PREFIX; +import org.netbeans.modules.php.editor.parser.PHPParseResult; +import org.netbeans.modules.php.editor.parser.astnodes.Expression; +import org.netbeans.modules.php.editor.parser.astnodes.FunctionInvocation; +import org.netbeans.modules.php.editor.parser.astnodes.Scalar; +import org.openide.filesystems.FileChangeAdapter; +import org.openide.filesystems.FileChangeListener; +import org.openide.filesystems.FileEvent; +import org.openide.filesystems.FileObject; +import org.openide.filesystems.FileUtil; +import org.openide.util.Exceptions; + +/** + * + * @author bhaidu + */ +public final class CustomDirectives { + + private final Project project; + private static final Map INSTANCES = new WeakHashMap<>(); + private final Map> customDirectives = new LinkedHashMap<>(); + + private List customDirectiveList = new ArrayList<>(); + + private final FileChangeListener fileChangeListener = new FileChangeListenerImpl(); + + private static final Logger LOGGER = Logger.getLogger(CustomDirectives.class.getName()); + + public static CustomDirectives getInstance(Project project) { + if (project == null) { + return new CustomDirectives(); + } + synchronized (INSTANCES) { + CustomDirectives customDirective = INSTANCES.get(project); + if (customDirective == null) { + customDirective = new CustomDirectives(project); + INSTANCES.put(project, customDirective); + } + return customDirective; + } + } + + public static CustomDirectives resetInstance(Project project) { + CustomDirectives customDirective = new CustomDirectives(project); + INSTANCES.put(project, customDirective); + return customDirective; + } + + private CustomDirectives() { + this.project = null; + } + + private CustomDirectives(Project project) { + this.project = project; + extractCustomDirectives(); + LOGGER.log(Level.INFO, "Finished extracting directives. Found ({0})", customDirectives.size()); // NOI18N + } + + private void extractCustomDirectives() { + LOGGER.info("Extracting custom directives"); // NOI18N + String[] compilerPathList = BladeProjectProperties.getInstance(project).getCompilerPathList(); + FileObject defaultAppProvider = project.getProjectDirectory().getFileObject(APP_PROVIDER_RELATIVE_PATH); + String defaultAppPath = ""; + + if (defaultAppProvider != null) { + addDirectiveNamesFromFile(defaultAppProvider); + File defaultAppFile = new File(defaultAppProvider.getPath()); + defaultAppPath = defaultAppFile.getAbsolutePath(); + FileUtil.addRecursiveListener(fileChangeListener, defaultAppFile); + } + + if (compilerPathList.length == 0) { + return; + } + for (String path : compilerPathList) { + if (path.equals("")) { // NOI18N + continue; + } + File file = new File(path); + if (!file.exists()) { + //remove + continue; + } + + String filePath = file.getPath(); + if (defaultAppPath.equals(filePath)) { + continue; + } + FileUtil.addRecursiveListener(fileChangeListener, file); + FileObject fileObj = FileUtil.toFileObject(file); + addDirectiveNamesFromFile(fileObj); + } + + } + + private void rescanFile(FileObject file) { + List entry = customDirectives.get(file); + if (entry.isEmpty()) { + addDirectiveNamesFromFile(file); + } + } + + public void addDirectiveNamesFromFile(FileObject file) { + final Source source = Source.create(file); + + if (source == null) { + return; + } + try { + ParserManager.parse(Collections.singleton(source), new UserTask() { + @Override + public void run(ResultIterator resultIterator) throws Exception { + PHPParseResult result = (PHPParseResult) resultIterator.getParserResult(); + if (result == null || result.getProgram() == null) { + return; + } + FunctionInvocationVisitor functionInvocationVisitor = new FunctionInvocationVisitor(); + result.getProgram().accept(functionInvocationVisitor); + List directiveList = functionInvocationVisitor.getDirectives(); + + if (directiveList.isEmpty()) { + return; + } + + customDirectiveList.addAll(directiveList); + customDirectives.put(file, directiveList); + } + }); + } catch (ParseException ex) { + Exceptions.printStackTrace(ex); + } + } + + public Map> getCustomDirectives() { + return customDirectives; + } + + public void filterAction(FilterCallback callback) { + for (Map.Entry> entry : customDirectives.entrySet()) { + if (!entry.getKey().isValid()) { + continue; + } + + for (CustomDirective directive : entry.getValue()) { + callback.filterDirectiveName(directive, entry.getKey()); + } + + } + } + + public void filterAction(FilterCallbackDeclaration callback) { + for (Map.Entry> entry : customDirectives.entrySet()) { + if (!entry.getKey().isValid()) { + continue; + } + + for (CustomDirective directive : entry.getValue()) { + callback.filterDirectiveName(directive, entry.getKey()); + } + + } + } + + public boolean customDirectiveConfigured(String query) { + for (CustomDirectives.CustomDirective customDirective : customDirectiveList) { + if (customDirective.getName().equals(query)) { + return true; + } + } + return false; + } + + public class DirectiveNames { + + private final List directiveNames; + + public DirectiveNames(List directiveNames) { + this.directiveNames = directiveNames; + } + + public List getList() { + return directiveNames; + } + } + + /** + * we are scanning the php ast nodes to search for the use of directive + * method the first parameter of the called method will be the custom + * directive name + */ + private class FunctionInvocationVisitor extends org.netbeans.modules.php.editor.parser.astnodes.visitors.DefaultVisitor { + + private final String[] validFunctions = new String[]{"directive", "if"}; // NOI18N + private final List directives; + + public FunctionInvocationVisitor() { + this.directives = new ArrayList<>(); + } + + @Override + public void scan(org.netbeans.modules.php.editor.parser.astnodes.ASTNode node) { + if (node != null) { + super.scan(node); + } + } + + @Override + public void visit(FunctionInvocation node) { + String functionName = node.getFunctionName().toString(); + if (!Arrays.stream(validFunctions).anyMatch(functionName::equals)) { + return; + } + List parameters = node.getParameters(); + Iterator iter = parameters.iterator(); + Expression directiveName = (Expression) iter.next(); + if (directiveName != null && directiveName instanceof Scalar) { + Scalar name = (Scalar) directiveName; + String escapedDirectiveName = name.getStringValue().replaceAll("^[\"|\']|[\"|[\']]$", ""); // NOI18N + + //Custom If Statements + if (functionName.equals("if")) { // NOI18N + directives.add(new CustomDirective("@" + escapedDirectiveName, name.getStartOffset(), true)); // NOI18N + directives.add(new CustomDirective("@unless" + escapedDirectiveName, name.getStartOffset())); // NOI18N + directives.add(new CustomDirective(DIRECTIVE_ELSE + escapedDirectiveName, name.getStartOffset())); + directives.add(new CustomDirective(END_DIRECTIVE_PREFIX + escapedDirectiveName, name.getStartOffset())); + } else { + directives.add(new CustomDirective("@" + escapedDirectiveName, name.getStartOffset())); // NOI18N + } + } + } + + public List getDirectives() { + return directives; + } + } + + private final class FileChangeListenerImpl extends FileChangeAdapter { + + @Override + public void fileFolderCreated(FileEvent fe) { + + } + + @Override + public void fileChanged(FileEvent fe) { + processFile(fe.getFile()); + } + + @Override + public void fileDataCreated(FileEvent fe) { + + } + + private void processFile(FileObject file) { + assert file.isData() : file; + CustomDirectives.getInstance(project).rescanFile(file); + } + + } + + public static interface FilterCallback { + + public void filterDirectiveName(CustomDirective directive, FileObject file); + } + + public static abstract class FilterCallbackDeclaration { + + private DeclarationFinder.DeclarationLocation location; + + public FilterCallbackDeclaration(DeclarationFinder.DeclarationLocation location) { + this.location = location; + } + + public void filterDirectiveName(CustomDirective directive, FileObject file) { + } + + public DeclarationFinder.DeclarationLocation getLocation() { + return location; + } + } + + public static class CustomDirective { + + private final String name; + private final int offset; + private final boolean isBlockDirective; + + public CustomDirective(String name, int offset) { + this(name, offset, false); + } + + public CustomDirective(String name) { + this(name, 0, false); + } + + public CustomDirective(String name, int offset, boolean isBlock) { + this.name = name; + this.offset = offset; + this.isBlockDirective = isBlock; + } + + public String getName() { + return name; + } + + public int getOffset() { + return offset; + } + + public boolean isBlockDirective() { + return isBlockDirective; + } + } +} diff --git a/php/php.blade/src/org/netbeans/modules/php/blade/editor/embedding/BladeHtmlEmbeddingProvider.java b/php/php.blade/src/org/netbeans/modules/php/blade/editor/embedding/BladeHtmlEmbeddingProvider.java new file mode 100644 index 000000000000..7f00011194f3 --- /dev/null +++ b/php/php.blade/src/org/netbeans/modules/php/blade/editor/embedding/BladeHtmlEmbeddingProvider.java @@ -0,0 +1,98 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.php.blade.editor.embedding; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import org.netbeans.api.lexer.Token; +import org.netbeans.api.lexer.TokenHierarchy; +import org.netbeans.api.lexer.TokenId; +import org.netbeans.api.lexer.TokenSequence; +import org.netbeans.modules.parsing.api.Embedding; +import org.netbeans.modules.parsing.api.Snapshot; +import org.netbeans.modules.parsing.spi.EmbeddingProvider; +import org.netbeans.modules.php.blade.editor.BladeLanguage; +import org.netbeans.modules.php.blade.editor.lexer.BladeTokenId; + +/** + * this will enable braces matches of html elements + * + * @author bhaidu + */ +@EmbeddingProvider.Registration( + mimeType = BladeLanguage.MIME_TYPE, + targetMimeType = "text/html") +public class BladeHtmlEmbeddingProvider extends EmbeddingProvider { + public static final String FILLER = " "; //NOI18N + public static final String TARGET_MIME_TYPE = "text/html"; //NOI18N + + @Override + public List getEmbeddings(final Snapshot snapshot) { + TokenHierarchy tokenHierarchy = snapshot.getTokenHierarchy(); + TokenSequence sequence = tokenHierarchy.tokenSequence(); + + if (sequence == null || !sequence.isValid()) { + return Collections.emptyList(); + } + sequence.moveStart(); + List embeddings = new ArrayList<>(); + + int offset = 0; + int len = 0; + + String fake; + + try { + while (sequence.moveNext()) { + Token t = sequence.token(); + offset = sequence.offset(); + TokenId id = t.id(); + len += t.length(); + String tText = t.text().toString(); + if (len == 0) { + continue; + } + if (id.equals(BladeTokenId.HTML)) { + embeddings.add(snapshot.create(offset, t.length(), TARGET_MIME_TYPE)); + } else { + fake = new String(new char[tText.length()]).replace("\0", FILLER); //NOI18N + embeddings.add(snapshot.create(fake, TARGET_MIME_TYPE)); + } + } + } catch (Exception ex) { + return Collections.emptyList(); + } + + if (embeddings.isEmpty()) { + return Collections.singletonList(snapshot.create("", TARGET_MIME_TYPE)); //NOI18N + } else { + return Collections.singletonList(Embedding.create(embeddings)); + } + } + + @Override + public int getPriority() { + return 210; + } + + @Override + public void cancel() { + } +} diff --git a/php/php.blade/src/org/netbeans/modules/php/blade/editor/embedding/BladePhpEmbeddingProvider.java b/php/php.blade/src/org/netbeans/modules/php/blade/editor/embedding/BladePhpEmbeddingProvider.java new file mode 100644 index 000000000000..8cde5cbd8179 --- /dev/null +++ b/php/php.blade/src/org/netbeans/modules/php/blade/editor/embedding/BladePhpEmbeddingProvider.java @@ -0,0 +1,94 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.php.blade.editor.embedding; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import org.netbeans.api.lexer.Token; +import org.netbeans.api.lexer.TokenHierarchy; +import org.netbeans.api.lexer.TokenId; +import org.netbeans.api.lexer.TokenSequence; +import org.netbeans.modules.parsing.api.Embedding; +import org.netbeans.modules.parsing.api.Snapshot; +import org.netbeans.modules.parsing.spi.EmbeddingProvider; +import org.netbeans.modules.php.api.util.FileUtils; +import org.netbeans.modules.php.blade.editor.BladeLanguage; +import org.netbeans.modules.php.blade.editor.lexer.BladeTokenId; + +/** + * only for simple content + * + * @author bogdan + */ +@EmbeddingProvider.Registration( + mimeType = BladeLanguage.MIME_TYPE, + targetMimeType = FileUtils.PHP_MIME_TYPE) +public class BladePhpEmbeddingProvider extends EmbeddingProvider { + public static final String TARGET_MIME_TYPE = FileUtils.PHP_MIME_TYPE; + + @Override + public List getEmbeddings(final Snapshot snapshot) { + TokenHierarchy tokenHierarchy = snapshot.getTokenHierarchy(); + TokenSequence sequence = tokenHierarchy.tokenSequence(); + if (sequence == null) { + return Collections.emptyList(); + } + sequence.moveStart(); + List embeddings = new ArrayList<>(); + + int offset = 0; + int len = 0; + + String fake; + + while (sequence.moveNext()) { + Token t = sequence.token(); + offset = sequence.offset(); + TokenId id = t.id(); + len += t.length(); + String tText = t.text().toString(); + if (len == 0) { + continue; + } + if (id.equals(BladeTokenId.PHP_INLINE)) { + embeddings.add(snapshot.create(offset, t.length(), TARGET_MIME_TYPE)); + } else { + fake = new String(new char[tText.length()]).replace("\0", "@"); //NOI18N + embeddings.add(snapshot.create(fake, TARGET_MIME_TYPE)); + } + } + + if (embeddings.isEmpty()) { + return Collections.singletonList(snapshot.create("", TARGET_MIME_TYPE)); //NOI18N + } else { + return Collections.singletonList(Embedding.create(embeddings)); + } + } + + @Override + public int getPriority() { + return 210; + } + + @Override + public void cancel() { + + } +} diff --git a/php/php.blade/src/org/netbeans/modules/php/blade/editor/format/BladeFormatter.java b/php/php.blade/src/org/netbeans/modules/php/blade/editor/format/BladeFormatter.java new file mode 100644 index 000000000000..84286e0b3d66 --- /dev/null +++ b/php/php.blade/src/org/netbeans/modules/php/blade/editor/format/BladeFormatter.java @@ -0,0 +1,140 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.php.blade.editor.format; + +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.prefs.Preferences; +import javax.swing.SwingUtilities; +import javax.swing.text.BadLocationException; +import javax.swing.text.Document; +import org.netbeans.modules.csl.api.Formatter; +import org.netbeans.modules.csl.spi.ParserResult; +import org.netbeans.modules.editor.indent.spi.Context; +import org.netbeans.api.editor.document.LineDocument; +import org.netbeans.api.editor.document.LineDocumentUtils; +import org.netbeans.api.editor.settings.SimpleValueNames; +import org.netbeans.modules.editor.indent.spi.CodeStylePreferences; +import org.netbeans.modules.php.blade.editor.preferences.GeneralPreferencesUtils; +import org.openide.util.Exceptions; +import org.openide.util.Lookup; + +/** + * + * @author bhaidu + */ +public class BladeFormatter implements Formatter { + private static final Logger LOGGER = Logger.getLogger(BladeFormatter.class.getName()); + + public BladeFormatter() { + } + + @Override + public void reformat(Context context, ParserResult compilationInfo) { + + LineDocument doc = LineDocumentUtils.as(context.document(), LineDocument.class); + if (doc == null) { + return; + } + if (context.isIndent() && !isBladeIndentEnabled(doc)) { + return; + } else if (!isBladeFormattingEnabled(doc)) { + return; + } + + int indentSize = getIndentSize(context.document()); + + long start = System.currentTimeMillis(); + Runnable rn = new Runnable() { + @Override + public void run() { + //the text can update between reformatting + LineDocument doc = LineDocumentUtils.as(context.document(), LineDocument.class); + if (doc == null) { + return; + } + try { + String currentText = doc.getText(0, doc.getLength()); + + if (context.isIndent()) { + int lineStart = context.lineStartOffset(context.caretOffset()); + String lineText = doc.getText(lineStart, context.caretOffset() - lineStart); + if (!lineText.isEmpty() && lineText.replaceAll(" ", "").isEmpty()) { //NOI18N + return; + } + } + (new BladeFormatterService()).format(context, currentText, indentSize); + + } catch (BadLocationException ex) { + Exceptions.printStackTrace(ex); + } + } + + }; + + String lookupClassName = Lookup.getDefault().getClass().getSimpleName(); + if (lookupClassName.equals("MockLookup")) { //NOI18N + //test mode + rn.run(); + } else { + SwingUtilities.invokeLater(rn); + } + //run after html indent is finished + + if (LOGGER.isLoggable(Level.FINE)) { + long end = System.currentTimeMillis(); + LOGGER.log(Level.FINE, "Reformat took: {0} ms", (end - start)); //NOI18N + } + } + + @Override + public void reindent(Context context) { + reformat(context, null); + } + + @Override + public boolean needsParserResult() { + return false; + } + + @Override + public int indentSize() { + return 4; + } + + @Override + public int hangingIndentSize() { + return 4; + } + + static int getIndentSize(Document doc) { + Preferences prefs = CodeStylePreferences.get(doc).getPreferences(); + return prefs.getInt(SimpleValueNames.INDENT_SHIFT_WIDTH, 4); + } + + static boolean isBladeIndentEnabled(Document doc) { + Preferences prefs = CodeStylePreferences.get(doc).getPreferences(); + return prefs.getBoolean(GeneralPreferencesUtils.ENABLE_INDENTATION, false); + } + + static boolean isBladeFormattingEnabled(Document doc) { + Preferences prefs = CodeStylePreferences.get(doc).getPreferences(); + return prefs.getBoolean(GeneralPreferencesUtils.ENABLE_FORMATTING, false); + } +} diff --git a/php/php.blade/src/org/netbeans/modules/php/blade/editor/format/BladeFormatterService.java b/php/php.blade/src/org/netbeans/modules/php/blade/editor/format/BladeFormatterService.java new file mode 100644 index 000000000000..bd3c0c71096c --- /dev/null +++ b/php/php.blade/src/org/netbeans/modules/php/blade/editor/format/BladeFormatterService.java @@ -0,0 +1,263 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.php.blade.editor.format; + +import java.util.Map; +import java.util.TreeMap; +import javax.swing.text.BadLocationException; +import org.antlr.v4.runtime.CharStreams; +import org.antlr.v4.runtime.CommonTokenStream; +import org.antlr.v4.runtime.ConsoleErrorListener; +import org.antlr.v4.runtime.Token; +import org.antlr.v4.runtime.tree.ParseTreeListener; +import org.netbeans.modules.editor.indent.spi.Context; +import org.netbeans.modules.php.blade.syntax.antlr4.formatter.BladeAntlrFormatterLexer; +import org.netbeans.modules.php.blade.syntax.antlr4.formatter.BladeAntlrFormatterParser; +import org.netbeans.modules.php.blade.syntax.antlr4.formatter.BladeAntlrFormatterParserBaseListener; +import org.openide.util.Exceptions; + +/** + * + * + * @author bhaidu + */ +public class BladeFormatterService { + public final Map formattedLineIndentList = new TreeMap<>(); + private boolean debugMode = false; + private boolean isIndentation; + + public void format(Context context, String text, int indentSize) { + isIndentation = context.isIndent(); + BladeAntlrFormatterLexer lexer = new BladeAntlrFormatterLexer(CharStreams.fromString(text)); + CommonTokenStream tokens = new CommonTokenStream(lexer); + BladeAntlrFormatterParser parser = new BladeAntlrFormatterParser(tokens); + parser.removeErrorListener(ConsoleErrorListener.INSTANCE); + parser.addParseListener(createFormatterListener()); + parser.setBuildParseTree(false); + parser.file(); + + final int cstart = context.startOffset(); + final int cend = context.endOffset(); + int textDelta = 0; + + for (Map.Entry entry : formattedLineIndentList.entrySet()) { + int tstart = entry.getValue().tokenStart; + int indent = entry.getValue().indent; + int htmlIndent = entry.getValue().htmlIndent; + if (tstart < context.document().getLength()) { + try { + //safety check of offset position + context.lineIndent(tstart); + } catch (BadLocationException ex) { + Exceptions.printStackTrace(ex); + break; + } + if (tstart > cend) { + break; + } + } + + if (tstart >= cstart) { + if (context.document().getLength() < (tstart - textDelta)) { + //skipping + continue; + } + + try { + int lineStart_i = context.lineStartOffset(tstart - textDelta); + int originalIndent_i = context.lineIndent(lineStart_i); + int wsIndent = (indent + htmlIndent) * indentSize; + + if (lineStart_i + wsIndent > context.document().getLength()) { + break; + } + context.modifyIndent(lineStart_i, wsIndent); + textDelta += (originalIndent_i - wsIndent); + } catch (BadLocationException ex) { + Exceptions.printStackTrace(ex); + break; + } + } + } + } + + private ParseTreeListener createFormatterListener() { + return new BladeAntlrFormatterParserBaseListener() { + int indent = 0; + int blockBalance = 0; + int htmlBlockBalance = 0; + int lastIncrementedLine = 0; + + @Override + public void exitBlock_start(BladeAntlrFormatterParser.Block_startContext ctx) { + + Token start = ctx.getStart(); + blockBalance++; + + if (!formattedLineIndentList.containsKey(start.getLine())) { + //hack to indent after blade block + int offset = isIndentation ? 1 : 0; + formattedLineIndentList.put(start.getLine(), + new FormatToken(start.getStopIndex() + 1 + offset, indent+offset, htmlBlockBalance, null)); + indent++; + } + } + + @Override + public void exitBlock_end(BladeAntlrFormatterParser.Block_endContext ctx) { + Token start = ctx.getStart(); + int line = start.getLine(); + indent--; + //correction + if (indent < 0) { + indent = 0; + } + if (!formattedLineIndentList.containsKey(line)) { + String debugText = debugMode ? start.getText() : null; + formattedLineIndentList.put(line, new FormatToken(start.getStartIndex(), indent, htmlBlockBalance, debugText)); + } else { + formattedLineIndentList.remove(line); + } + + blockBalance--; + } + + @Override + public void enterSection_block(BladeAntlrFormatterParser.Section_blockContext ctx) { + Token start = ctx.getStart(); + + blockBalance++; + if (!formattedLineIndentList.containsKey(start.getLine())) { + Token rArgParent = ctx.getStart(); + String debugText = debugMode ? rArgParent.getText() : null; + formattedLineIndentList.put(rArgParent.getLine(), new FormatToken(rArgParent.getStopIndex() + 1, indent, htmlBlockBalance, debugText)); + indent++; + } + } + + @Override + public void exitSection_block(BladeAntlrFormatterParser.Section_blockContext ctx) { + Token end = ctx.getStop(); + + int line = end.getLine(); + if (!formattedLineIndentList.containsKey(line)) { + indent--; + if (indent < 0) { + indent = 0; + } + formattedLineIndentList.put(line, new FormatToken(end.getStartIndex(), indent, htmlBlockBalance, end.getText())); + } else { + formattedLineIndentList.remove(line); + } + //correction + if (indent < 0) { + indent = 0; + } + blockBalance--; + } + + @Override + public void exitInline_identable_element(BladeAntlrFormatterParser.Inline_identable_elementContext ctx) { + Token start = ctx.getStart(); + int line = start.getLine(); + if (!formattedLineIndentList.containsKey(line)) { + if ((htmlBlockBalance > 0 || blockBalance > 0)) { + formattedLineIndentList.put(line, new FormatToken(start.getStartIndex(), indent, htmlBlockBalance, start.getText())); + } + } + } + + @Override + public void exitNl_with_space(BladeAntlrFormatterParser.Nl_with_spaceContext ctx) { + if (ctx.WS().isEmpty()) { + return; + } + + Token start = ctx.WS(0).getSymbol(); + int line = start.getLine(); + if (!formattedLineIndentList.containsKey(line)) { + formattedLineIndentList.put(line, new FormatToken(start.getStartIndex(), indent, "ws")); + } + } + + @Override + public void exitHtml_close_tag(BladeAntlrFormatterParser.Html_close_tagContext ctx) { + + Token start = ctx.getStart(); + int line = start.getLine(); + if (line > 0 && lastIncrementedLine != line){ + if (htmlBlockBalance > 0) { + htmlBlockBalance--; + } else { + htmlBlockBalance = 0; + } + lastIncrementedLine = line; + } + if (!formattedLineIndentList.containsKey(line)) { + formattedLineIndentList.put(line, new FormatToken(start.getStartIndex(), indent, htmlBlockBalance, start.getText())); + } + } + + @Override + public void exitHtml_tag(BladeAntlrFormatterParser.Html_tagContext ctx) { + Token start = ctx.getStart(); + int line = start.getLine(); + if (!formattedLineIndentList.containsKey(line)) { + if ((htmlBlockBalance > 0 || blockBalance > 0)) { + formattedLineIndentList.put(line, new FormatToken(start.getStartIndex(), indent, htmlBlockBalance, start.getText())); + } + htmlBlockBalance++; + } + } + + @Override + public void exitSelf_closed_tag(BladeAntlrFormatterParser.Self_closed_tagContext ctx) { + Token start = ctx.getStart(); + int line = start.getLine(); + if (!formattedLineIndentList.containsKey(line)) { + if ((htmlBlockBalance > 0 || blockBalance > 0)) { + formattedLineIndentList.put(line, new FormatToken(start.getStartIndex(), indent, htmlBlockBalance, start.getText())); + } + } + } + + @Override + public void exitHtml_indent(BladeAntlrFormatterParser.Html_indentContext ctx) { + Token start = ctx.getStart(); + int line = start.getLine(); + if (!formattedLineIndentList.containsKey(line)) { + String debugText = debugMode ? start.getText() : null; + formattedLineIndentList.put(line, new FormatToken(start.getStartIndex(), indent, htmlBlockBalance, debugText)); + } + htmlBlockBalance++; + } + + @Override + public void exitBlock_aligned_directive(BladeAntlrFormatterParser.Block_aligned_directiveContext ctx) { + Token start = ctx.getStart(); + int line = start.getLine(); + if (!formattedLineIndentList.containsKey(line)) { + if ((htmlBlockBalance > 0 || blockBalance > 0)) { + formattedLineIndentList.put(line, new FormatToken(start.getStartIndex(), indent - 1, htmlBlockBalance, start.getText())); + } + } + } + }; + } +} diff --git a/php/php.blade/src/org/netbeans/modules/php/blade/editor/format/FormatToken.java b/php/php.blade/src/org/netbeans/modules/php/blade/editor/format/FormatToken.java new file mode 100644 index 000000000000..b3a70ffd76d2 --- /dev/null +++ b/php/php.blade/src/org/netbeans/modules/php/blade/editor/format/FormatToken.java @@ -0,0 +1,50 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.php.blade.editor.format; + +/** + * + * @author bhaidu + */ +public class FormatToken { + + public int tokenStart; + public int indent; + public int htmlIndent; + //for directive + public String directive; + + public FormatToken(int tokenStart, int indent, String directive) { + this.tokenStart = tokenStart; + this.indent = indent; + this.directive = directive; + } + + public FormatToken(int tokenStart, int indent, int htmlIndent, String directive) { + this.tokenStart = tokenStart; + this.indent = indent; + this.directive = directive; + this.htmlIndent = htmlIndent; + } + + @Override + public String toString() { + return this.directive + ", " + this.indent * 4 + ", " + this.tokenStart; //NOI18N + } +} diff --git a/php/php.blade/src/org/netbeans/modules/php/blade/editor/highlighting/BladeHighlightsContainer.java b/php/php.blade/src/org/netbeans/modules/php/blade/editor/highlighting/BladeHighlightsContainer.java new file mode 100644 index 000000000000..67cda30fe116 --- /dev/null +++ b/php/php.blade/src/org/netbeans/modules/php/blade/editor/highlighting/BladeHighlightsContainer.java @@ -0,0 +1,83 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.php.blade.editor.highlighting; + +import javax.swing.text.AbstractDocument; +import javax.swing.text.AttributeSet; +import org.netbeans.modules.php.blade.editor.BladeLanguage; +import org.netbeans.spi.editor.highlighting.HighlightsSequence; +import org.netbeans.spi.editor.highlighting.support.AbstractHighlightsContainer; + + +/** + * + * hack to fix the highlighting issue on javascript properties vs blade paths + * "@include('my.path') - my.path should be fully selected on double click + * window.test - should not be fully selected on double click + * + * @author bhaidu + */ +public class BladeHighlightsContainer extends AbstractHighlightsContainer { + + private final AbstractDocument doc; + AttributeSet attrs = null; + private static int offset = 0; + + public BladeHighlightsContainer(AbstractDocument doc) { + this.doc = doc; + } + + public @Override + HighlightsSequence getHighlights(final int startOffset, final int endOffset) { + return new HighlightsSequence() { + int start, end; + final int scanStart; + + { + scanStart = doc.getParagraphElement(startOffset).getStartOffset(); + if (offset != scanStart){ + offset = scanStart; + //reset the hasQuote flag from isIdentifierChar + BladeLanguage.hasQuote = false; + } + } + + public @Override + boolean moveNext() { + + return false; + } + + public @Override + int getStartOffset() { + return start; + } + + public @Override + int getEndOffset() { + return end; + } + + public @Override + AttributeSet getAttributes() { + return attrs; + } + }; + } +} diff --git a/php/php.blade/src/org/netbeans/modules/php/blade/editor/highlighting/BladeHighlightsLayerFactory.java b/php/php.blade/src/org/netbeans/modules/php/blade/editor/highlighting/BladeHighlightsLayerFactory.java new file mode 100644 index 000000000000..3752ee15ad56 --- /dev/null +++ b/php/php.blade/src/org/netbeans/modules/php/blade/editor/highlighting/BladeHighlightsLayerFactory.java @@ -0,0 +1,45 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.php.blade.editor.highlighting; + +import javax.swing.text.AbstractDocument; +import org.netbeans.api.editor.mimelookup.MimeRegistration; +import org.netbeans.modules.php.blade.editor.BladeLanguage; +import org.netbeans.spi.editor.highlighting.HighlightsLayer; + +import org.netbeans.spi.editor.highlighting.HighlightsLayerFactory; +import org.netbeans.spi.editor.highlighting.ZOrder; + + +/** + * hack to fix the highlighting issue on javascript properties vs blade paths + * "@include('my.path') - my.path should be fully selected on double click + * window.test - should not be fully selected on double click + * + * @author bhaidu + */ +@MimeRegistration(service=HighlightsLayerFactory.class, mimeType=BladeLanguage.MIME_TYPE, position=200) +public class BladeHighlightsLayerFactory implements HighlightsLayerFactory { + + public @Override HighlightsLayer[] createLayers(final Context context) { + return new HighlightsLayer[] {HighlightsLayer.create("blade", ZOrder.SYNTAX_RACK.forPosition(10), true, + new BladeHighlightsContainer((AbstractDocument) context.getDocument()))}; + } + +} diff --git a/php/php.blade/src/org/netbeans/modules/php/blade/editor/hints/BladeHintsProvider.java b/php/php.blade/src/org/netbeans/modules/php/blade/editor/hints/BladeHintsProvider.java new file mode 100644 index 000000000000..96dbd6baf5ed --- /dev/null +++ b/php/php.blade/src/org/netbeans/modules/php/blade/editor/hints/BladeHintsProvider.java @@ -0,0 +1,231 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.php.blade.editor.hints; + +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Set; +import org.netbeans.api.editor.document.EditorDocumentUtils; +import org.netbeans.api.project.Project; +import org.netbeans.modules.csl.api.Error; +import org.netbeans.modules.csl.api.Hint; +import org.netbeans.modules.csl.api.HintSeverity; +import org.netbeans.modules.csl.api.OffsetRange; +import org.netbeans.modules.csl.api.Rule; +import org.netbeans.modules.csl.api.Rule.ErrorRule; +import org.netbeans.modules.csl.api.RuleContext; +import org.netbeans.modules.csl.api.HintsProvider; +import org.netbeans.modules.php.blade.editor.BladeLanguage; +import org.netbeans.modules.php.blade.editor.directives.CustomDirectives; +import org.netbeans.modules.php.blade.editor.parser.BladeParserResult; +import org.netbeans.modules.php.blade.editor.path.BladePathUtils; +import org.netbeans.modules.php.blade.project.ProjectUtils; +import org.openide.filesystems.FileObject; + +/** + * + * @author bhaidu + */ +public class BladeHintsProvider implements HintsProvider { + + /** + * Compute hints applicable to the given compilation info and add to the + * given result list. + * + * @param manager + * @param context + * @param hints + */ + @Override + public void computeHints(HintsManager manager, RuleContext context, List hints) { + if (!(context.parserResult instanceof BladeParserResult)) { + return; + } + Map> allHints = manager.getHints(false, context); + List directiveHints = allHints.get("blade.option.directive.hints"); //NOI18N + + BladeParserResult parserResult = (BladeParserResult) context.parserResult; + FileObject fo = EditorDocumentUtils.getFileObject(context.doc); + Project project = ProjectUtils.getMainOwner(fo); + CustomDirectives ct = CustomDirectives.getInstance(project); + + if (directiveHints != null) { + for (Rule.AstRule astRule : directiveHints) { + if (!manager.isEnabled(astRule)) { + continue; + } + if (astRule instanceof UnknownDirective) { + for (Map.Entry entry : parserResult.getBladeCustomDirectiveOccurences().getAll().entrySet()) { + if (ct.customDirectiveConfigured(entry.getValue())) { + continue; + } + hints.add(new Hint(astRule, + "Unknown directive. Try adding the provider class file using Project -> Properties -> Custom Directives", //NOI18N + context.parserResult.getSnapshot().getSource().getFileObject(), + entry.getKey(), + Collections.emptyList(), + 10)); + } + } + } + } + + //validate path config + for (Map.Entry> entry : parserResult.getBladeReferenceIdsCollection().getIncludePathsOccurences().entrySet()) { + FileObject realFile = BladePathUtils.findFileObjectForBladeViewPath(parserResult.getFileObject(), + entry.getKey()); + if (realFile != null) { + continue; + } + for (OffsetRange range : entry.getValue()) { + OffsetRange hintRange = new OffsetRange(range.getStart(), range.getEnd() + 1); + hints.add(new Hint(new BladeRule(HintSeverity.WARNING), + "Blade path not found.\nFor custom blade context you can try to set the root folder using:\nProject -> Properties -> Laravel Blade -> Views Folder", //NOI18N + context.parserResult.getSnapshot().getSource().getFileObject(), + hintRange, + Collections.emptyList(), + 10)); + } + } + + } + + /** + * Compute any suggestions applicable to the given caret offset, and add to + * the given suggestion list. + */ + @Override + public void computeSuggestions(HintsManager manager, RuleContext context, List suggestions, int caretOffset) { + } + + /** + * Compute any suggestions applicable to the given caret offset, and add to + * the given suggestion list. + */ + @Override + public void computeSelectionHints(HintsManager manager, RuleContext context, List suggestions, int start, int end) { + } + + /** + * Process the errors for the given compilation info, and add errors and + * warning descriptions into the provided hint list. Return any errors that + * were not added as error descriptions (e.g. had no applicable error rule) + */ + @Override + public void computeErrors(HintsManager manager, RuleContext context, List hints, List unhandled) { + unhandled.addAll(context.parserResult.getDiagnostics()); + } + + /** + * Cancel in-progress processing of hints. + */ + @Override + public void cancel() { + //not required + } + + /** + *

+ * Optional builtin Rules. Typically you don't use this; you register your + * rules in your filesystem layer in the gsf-hints/mimetype1/mimetype2 + * folder, for example gsf-hints/text/x-ruby/. Error hints should go in the + * "errors" folder, selection hints should go in the "selection" folder, and + * all other hints should go in the "hints" folder (but note that you can + * create localized folders and organize them under hints; these categories + * are shown in the hints options panel. Hints returned from this method + * will be placed in the "general" folder. + *

+ *

+ * This method is primarily intended for rules that should be added + * dynamically, for example for Rules that have a many different flavors yet + * a single implementation class (such as JavaScript's StrictWarning rule + * which wraps a number of builtin parser warnings.) + * + * @return A list of rules that are builtin, or null or an empty list when + * there are no builtins + */ + @Override + public List getBuiltinRules() { + return Collections.emptyList(); + } + + /** + * Create a RuleContext object specific to this HintsProvider. This lets + * implementations of this interface created subclasses of the RuleContext + * that can be passed around to all the executed rules. + * + * @return A new instance of a RuleContext object + */ + @Override + public RuleContext createRuleContext() { + return new BladeRuleContext(); + } + + private static final class BladeRule implements ErrorRule { + + private final HintSeverity severity; + + private BladeRule(HintSeverity severity) { + this.severity = severity; + } + + @Override + public Set getCodes() { + return Collections.emptySet(); + } + + @Override + public boolean appliesTo(RuleContext context) { + return true; + } + + @Override + public String getDisplayName() { + return "blade"; //NOI18N + } + + @Override + public boolean showInTasklist() { + return true; + } + + @Override + public HintSeverity getDefaultSeverity() { + return severity; + } + } + + public class BladeRuleContext extends RuleContext { + + private BladeParserResult bladeParserResult = null; + + public BladeParserResult getJsParserResult() { + if (bladeParserResult == null) { + bladeParserResult = (BladeParserResult) parserResult; + } + return bladeParserResult; + } + + public boolean isCancelled() { + return false; + } + + } +} diff --git a/php/php.blade/src/org/netbeans/modules/php/blade/editor/hints/Bundle.properties b/php/php.blade/src/org/netbeans/modules/php/blade/editor/hints/Bundle.properties new file mode 100644 index 000000000000..eac084f5fafe --- /dev/null +++ b/php/php.blade/src/org/netbeans/modules/php/blade/editor/hints/Bundle.properties @@ -0,0 +1,27 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +UnknownDirectiveHintMsg=Unknown directive. Try adding the provider class file using Project -> Properties -> Custom Directives +BladeViewNotFound=Blade path not found.\ +For custom blade context you can try to set the root folder using:\ +Project -> Properties -> Laravel Blade -> Views Folder + +AST_Rule_UnknownDirective=Unknown Directive +AST_Rule_UnknownDirectiveDescription=Unknown Directive. The directive my not be in the base laravel blade compiler and was not found in the custom list. + +AST_Rule_PhpSyntaxError=`@php` directive syntax error +AST_Rule_PhpSyntaxErrorDescription=Php Syntax error for `@php` directives diff --git a/php/php.blade/src/org/netbeans/modules/php/blade/editor/hints/HintsControllerFactory.java b/php/php.blade/src/org/netbeans/modules/php/blade/editor/hints/HintsControllerFactory.java new file mode 100644 index 000000000000..57b23a072f74 --- /dev/null +++ b/php/php.blade/src/org/netbeans/modules/php/blade/editor/hints/HintsControllerFactory.java @@ -0,0 +1,47 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.php.blade.editor.hints; + +import org.netbeans.modules.csl.api.HintsProvider; +import org.netbeans.modules.php.blade.editor.BladeLanguage; +import org.netbeans.spi.options.OptionsPanelController; +import org.openide.util.NbBundle; + +/** + * + * @author bogdan + */ +public class HintsControllerFactory { + + public HintsControllerFactory() { + } + + @OptionsPanelController.SubRegistration( + id = "BladeHints", + location = "Blade/Hints", + displayName = "#HintsControllerFactory.name" + ) + @NbBundle.Messages("HintsControllerFactory.name=Blade Hints") + public static OptionsPanelController createOptions() { + HintsProvider.HintsManager manager = HintsProvider.HintsManager.getManagerForMimeType(BladeLanguage.MIME_TYPE); + assert manager != null; + + return manager.getOptionsController(); + } +} diff --git a/php/php.blade/src/org/netbeans/modules/php/blade/editor/hints/UnknownDirective.java b/php/php.blade/src/org/netbeans/modules/php/blade/editor/hints/UnknownDirective.java new file mode 100644 index 000000000000..7bb522aa035d --- /dev/null +++ b/php/php.blade/src/org/netbeans/modules/php/blade/editor/hints/UnknownDirective.java @@ -0,0 +1,87 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.php.blade.editor.hints; + +import java.util.Collections; +import java.util.List; +import java.util.Set; +import java.util.prefs.Preferences; +import javax.swing.JComponent; +import javax.swing.text.BadLocationException; +import org.netbeans.modules.csl.api.Hint; +import org.netbeans.modules.csl.api.HintSeverity; +import org.netbeans.modules.csl.api.HintsProvider; +import org.netbeans.modules.csl.api.Rule; +import org.netbeans.modules.csl.api.RuleContext; + +/** + * + * @author bogdan + */ +public class UnknownDirective implements Rule.AstRule { + + public void computeHints(RuleContext context, List hints, int offset, HintsProvider.HintsManager manager) throws BadLocationException { + + } + + @Override + public boolean getDefaultEnabled() { + return true; + } + + @Override + public JComponent getCustomizer(Preferences node) { + return null; + } + + @Override + public boolean appliesTo(RuleContext context) { + return context instanceof BladeHintsProvider.BladeRuleContext; + } + + @Override + public boolean showInTasklist() { + return true; + } + + @Override + public HintSeverity getDefaultSeverity() { + return HintSeverity.WARNING; + } + + @Override + public Set getKinds() { + return Collections.singleton("blade.option.directive.hints"); + } + + @Override + public String getId() { + return "blade.hint.unknown_directive"; + } + + @Override + public String getDescription() { + return "Unknown Directive. The directive my not be in the base laravel blade compiler and was not found in the custom list."; + } + + @Override + public String getDisplayName() { + return "Unknown Directive"; + } +} diff --git a/php/php.blade/src/org/netbeans/modules/php/blade/editor/indexing/BladeIndex.java b/php/php.blade/src/org/netbeans/modules/php/blade/editor/indexing/BladeIndex.java new file mode 100644 index 000000000000..4859e7f55b18 --- /dev/null +++ b/php/php.blade/src/org/netbeans/modules/php/blade/editor/indexing/BladeIndex.java @@ -0,0 +1,321 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.php.blade.editor.indexing; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.WeakHashMap; +import java.util.concurrent.ExecutionException; +import org.netbeans.api.project.Project; +import org.netbeans.api.project.ui.OpenProjects; +import org.netbeans.modules.csl.api.OffsetRange; +import org.netbeans.modules.parsing.spi.indexing.support.IndexResult; +import org.netbeans.modules.parsing.spi.indexing.support.QuerySupport; +import org.netbeans.modules.php.blade.editor.parser.BladeParserResult.Reference; +import org.openide.filesystems.FileObject; +import org.openide.util.Exceptions; + +/** + * + * @author bhaidu + */ +public class BladeIndex { + + private final QuerySupport querySupport; + private static final Map INDEXES = new WeakHashMap<>(); + private static boolean areProjectsOpen = false; + + private BladeIndex(QuerySupport querySupport) throws IOException { + this.querySupport = querySupport; + } + + public QuerySupport getQuerySupport() { + return querySupport; + } + + public static BladeIndex get(Project project) throws IOException { + if (project == null) { + return null; + } + synchronized (INDEXES) { + BladeIndex index = INDEXES.get(project); + if (index == null) { + if (!areProjectsOpen) { + try { + // just be sure that the projects are open + OpenProjects.getDefault().openProjects().get(); + } catch (InterruptedException | ExecutionException ex) { + Exceptions.printStackTrace(ex); + } finally { + areProjectsOpen = true; + } + } + Collection sourceRoots = QuerySupport.findRoots(project, + null /* all source roots */, + Collections.emptyList(), + Collections.emptyList()); + QuerySupport querySupport = QuerySupport.forRoots(BladeIndexer.Factory.NAME, BladeIndexer.Factory.VERSION, sourceRoots.toArray(new FileObject[]{})); + index = new BladeIndex(querySupport); + if (!sourceRoots.isEmpty()) { + INDEXES.put(project, index); + } + } + return index; + } + } + + public List queryYieldIds(String prefix) { + return queryIndexedReferenceId(prefix, BladeIndexer.YIELD_ID); + } + + public List queryStacksIndexedReferences(String prefix) { + return queryIndexedReferenceId(prefix, BladeIndexer.STACK_ID); + } + + private List queryIndexedReferenceId(String prefix, String indexKey) { + List indexedReferences = new ArrayList<>(); + + try { + Collection result = querySupport.query(indexKey, prefix, QuerySupport.Kind.PREFIX, indexKey); + + if (result == null || result.isEmpty()) { + return indexedReferences; + } + + for (IndexResult indexResult : result) { + String[] values = indexResult.getValues(indexKey); + for (String value : values) { + if (value.startsWith(prefix)) { + indexedReferences.add(new IndexedReferenceId(value, indexResult.getFile())); + } + } + } + } catch (IOException ex) { + Exceptions.printStackTrace(ex); + } + + return indexedReferences; + } + + public List queryYieldIndexedReferences(String prefix) { + return queryIndexedReferences(prefix, + BladeIndexer.YIELD_REFERENCE, + new IndexReferenceCallback() { + @Override + public Reference createIndexReference(String value) { + return BladeIndexer.extractYieldDataFromIndex(value); + } + } + ); + } + + public List queryStacksIdsReference(String prefix) { + return queryIndexedReferences(prefix, + BladeIndexer.STACK_REFERENCE, + new IndexReferenceCallback() { + @Override + public Reference createIndexReference(String value) { + return BladeIndexer.extractStackDataFromIndex(value); + } + } + ); + } + + private List queryIndexedReferences(String prefix, String indexKey, IndexReferenceCallback callback) { + List references = new ArrayList<>(); + try { + Collection result = querySupport.query(indexKey, + prefix, QuerySupport.Kind.PREFIX, indexKey); + + if (result == null || result.isEmpty()) { + return references; + } + + for (IndexResult indexResult : result) { + String[] values = indexResult.getValues(indexKey); + for (String value : values) { + if (value.startsWith(prefix)) { + references.add(new IndexedReference(callback.createIndexReference(value), indexResult.getFile())); + } + } + } + } catch (IOException ex) { + Exceptions.printStackTrace(ex); + } + + return references; + } + + public List findYieldIndexedReferences(String prefix) { + return findIndexedReferences(prefix, + BladeIndexer.YIELD_ID, + new String[]{BladeIndexer.YIELD_ID, BladeIndexer.YIELD_REFERENCE}, + BladeIndexer.YIELD_REFERENCE, + new IndexReferenceCallback() { + @Override + public Reference createIndexReference(String value) { + return BladeIndexer.extractYieldDataFromIndex(value); + } + } + ); + } + + public List findStackIdIndexedReferences(String prefix) { + return findIndexedReferences(prefix, + BladeIndexer.STACK_ID, + new String[]{BladeIndexer.STACK_ID, BladeIndexer.STACK_REFERENCE}, + BladeIndexer.STACK_REFERENCE, + new IndexReferenceCallback() { + @Override + public Reference createIndexReference(String value) { + return BladeIndexer.extractStackDataFromIndex(value); + } + } + ); + } + + private List findIndexedReferences(String prefix, + String indexKey, String[] valuesKeys, String valueKey, + IndexReferenceCallback callback) { + List references = new ArrayList<>(); + try { + Collection result = querySupport.query(indexKey, + prefix, QuerySupport.Kind.EXACT, valuesKeys); + + if (result == null || result.isEmpty()) { + return references; + } + + for (IndexResult indexResult : result) { + String[] values = indexResult.getValues(valueKey); + for (String value : values) { + String name = BladeIndexer.getIdFromSignature(value); + if (name != null && name.equals(prefix)) { + references.add( + new IndexedReference(callback.createIndexReference(value), + indexResult.getFile())); + } + } + } + } catch (IOException ex) { + Exceptions.printStackTrace(ex); + } + + return references; + } + + public List getIncludePaths(String prefix) { + List references = new ArrayList<>(); + Collection result; + try { + result = querySupport.query(BladeIndexer.INCLUDE_PATH, prefix, QuerySupport.Kind.PREFIX, BladeIndexer.INCLUDE_PATH); + + if (result == null || result.isEmpty()) { + return references; + } + + for (IndexResult indexResult : result) { + String[] values = indexResult.getValues(BladeIndexer.INCLUDE_PATH); + for (String value : values) { + Reference templatePathRef = BladeIndexer.extractTemplatePathDataFromIndex(value); + if (!templatePathRef.identifier.equals(prefix)) { + continue; + } + references.add(new IndexedOffsetReference(templatePathRef.identifier, indexResult.getFile(), templatePathRef.defOffset)); + } + } + } catch (IOException ex) { + Exceptions.printStackTrace(ex); + } + return references; + } + + public static interface IndexReferenceCallback { + + public Reference createIndexReference(String value); + } + + public static class IndexedReferenceId { + + private final String identifier; + private final FileObject originFile; + + public IndexedReferenceId(String identifier, FileObject originFile) { + this.identifier = identifier; + this.originFile = originFile; + } + + public String getIdenfiier() { + return this.identifier; + } + + public FileObject getOriginFile() { + return this.originFile; + } + } + + public static class IndexedReference { + + private final Reference reference; + private final FileObject originFile; + + public IndexedReference(Reference reference, FileObject originFile) { + this.reference = reference; + this.originFile = originFile; + } + + public Reference getReference() { + return this.reference; + } + + public FileObject getOriginFile() { + return this.originFile; + } + } + + public static class IndexedOffsetReference { + + private final String identifier; + private final FileObject originFile; + private final OffsetRange range; + + public IndexedOffsetReference(String identifier, + FileObject originFile, OffsetRange range) { + this.identifier = identifier; + this.originFile = originFile; + this.range = range; + } + + public String getReference() { + return this.identifier; + } + + public FileObject getOriginFile() { + return this.originFile; + } + + public int getStart() { + return this.range.getStart(); + } + } +} diff --git a/php/php.blade/src/org/netbeans/modules/php/blade/editor/indexing/BladeIndexer.java b/php/php.blade/src/org/netbeans/modules/php/blade/editor/indexing/BladeIndexer.java new file mode 100644 index 000000000000..923cfd351097 --- /dev/null +++ b/php/php.blade/src/org/netbeans/modules/php/blade/editor/indexing/BladeIndexer.java @@ -0,0 +1,303 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.php.blade.editor.indexing; + +import java.io.IOException; +import java.util.List; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; +import org.netbeans.api.editor.mimelookup.MimeRegistration; +import org.netbeans.api.project.Project; +import org.netbeans.modules.csl.api.OffsetRange; +import org.netbeans.modules.parsing.api.Snapshot; +import org.netbeans.modules.parsing.spi.Parser; +import org.netbeans.modules.parsing.spi.indexing.Context; +import org.netbeans.modules.parsing.spi.indexing.EmbeddingIndexer; +import org.netbeans.modules.parsing.spi.indexing.EmbeddingIndexerFactory; +import org.netbeans.modules.parsing.spi.indexing.Indexable; +import org.netbeans.modules.parsing.spi.indexing.support.IndexingSupport; +import org.netbeans.modules.parsing.spi.indexing.support.IndexDocument; +import org.netbeans.modules.php.blade.editor.BladeLanguage; +import org.netbeans.modules.php.blade.editor.parser.BladeParserResult; +import org.netbeans.modules.php.blade.editor.parser.BladeParserResult.Reference; +import org.netbeans.modules.php.blade.editor.parser.BladeParserResult.ReferenceType; +import org.netbeans.modules.php.blade.editor.parser.BladeReferenceIdsCollection; +import org.netbeans.modules.php.blade.editor.path.BladePathUtils; +import org.netbeans.modules.php.blade.project.ProjectUtils; +import org.netbeans.modules.php.blade.syntax.StringUtils; +import org.openide.filesystems.FileObject; +import org.openide.util.Exceptions; + +/** + * move to language EmbeddingIndexerFactory getIndexerFactory ? + * + * @author bhaidu + */ +public class BladeIndexer extends EmbeddingIndexer { + + private static final Logger LOGGER = Logger.getLogger(BladeIndexer.class.getSimpleName()); + public static final String BLADE_INDEXED = "indexed"; //NOI18N + public static final String YIELD_REFERENCE = "yield"; //NOI18N + public static final String YIELD_ID = "yieldid"; //NOI18N + public static final String STACK_REFERENCE = "stack"; //NOI18N + public static final String STACK_ID = "stackid"; //NOI18N + public static final String INCLUDE_PATH = "include"; //NOI18N + public static final String BLADE_PATH = "path"; //NOI18N + public static final String INFO_SEPARATOR = "#"; //NOI18N + public static final String RANGE_SEPARATOR = ";"; //NOI18N + + @Override + protected void index(Indexable indxbl, Parser.Result result, Context context) { + long startTime = System.currentTimeMillis(); + + BladeParserResult parserResult; + + if (!(result instanceof BladeParserResult)) { + return; + } + + parserResult = (BladeParserResult) result; + + if (!parserResult.getDiagnostics().isEmpty()) { + return; + } + + try { + IndexingSupport support = IndexingSupport.getInstance(context); + + support.removeDocuments(indxbl); + IndexDocument document = support.createDocument(indxbl); + + BladeReferenceIdsCollection referenceCollection = parserResult.getBladeReferenceIdsCollection(); + if (!referenceCollection.getYieldIdOccurences().isEmpty()) { + storeYieldReferences(referenceCollection.getYieldIdOccurences(), document); + } + + if (!referenceCollection.getStackIdOccurences().isEmpty()) { + storeStackReferences(referenceCollection.getStackIdOccurences(), document); + } + + if (!referenceCollection.getIncludePathsOccurences().isEmpty()) { + storeIncludePathReferences(referenceCollection.getIncludePathsOccurences(), document); + } + + storeFilePathAsBladePath(parserResult.getSnapshot().getSource().getFileObject(), document); + + document.addPair(BLADE_INDEXED, Boolean.TRUE.toString(), true, true); + + support.addDocument(document); + long time = System.currentTimeMillis() - startTime; + + if (time > 2000) { + LOGGER.log(Level.INFO, "Indexer for " + context.getIndexFolder().getName() + " finished in {0} ms", System.currentTimeMillis() - startTime); + } + } catch (IOException ex) { + LOGGER.log(Level.WARNING, null, ex); + Exceptions.printStackTrace(ex); + } catch (Exception ex) { + Exceptions.printStackTrace(ex); + } + } + + private void storeYieldReferences(Map yields, IndexDocument document) { + + for (Map.Entry entry : yields.entrySet()) { + StringBuilder sb = new StringBuilder(); + OffsetRange range = entry.getValue(); + //used for completion + document.addPair(YIELD_ID, entry.getKey(), true, true); + sb.append(entry.getKey()).append(INFO_SEPARATOR).append(range.getStart()).append(RANGE_SEPARATOR).append(range.getEnd()); + //used for declaration finder + document.addPair(YIELD_REFERENCE, sb.toString(), true, true); + } + } + + private void storeStackReferences(Map stacks, IndexDocument document) { + + for (Map.Entry entry : stacks.entrySet()) { + StringBuilder sb = new StringBuilder(); + OffsetRange range = entry.getValue(); + //used for completion + document.addPair(STACK_ID, entry.getKey(), true, true); + + sb.append(entry.getKey()).append(INFO_SEPARATOR).append(range.getStart()).append(RANGE_SEPARATOR).append(range.getEnd()); + //used for declaration finder + document.addPair(STACK_REFERENCE, sb.toString(), true, true); + } + } + + private void storeFilePathAsBladePath(FileObject fo, IndexDocument document) { + Project project = ProjectUtils.getMainOwner(fo); + if (project == null) { + return; + } + List roots = BladePathUtils.getCustomViewsRoots(project, fo); + String filePath = fo.getPath(); + + for (FileObject root : roots) { + String rootPath = root.getPath(); + if (filePath.startsWith(rootPath)) { + String bladeFormatPath = BladePathUtils.toBladeViewPath(filePath.replace(rootPath, "")); //NOI18N + if (bladeFormatPath.startsWith(StringUtils.DOT)) { + bladeFormatPath = bladeFormatPath.substring(1, bladeFormatPath.length()); + } + document.addPair(BLADE_PATH, bladeFormatPath, true, true); + } + } + } + + public static Reference extractYieldDataFromIndex(String index) { + String[] mainElements = index.split(INFO_SEPARATOR); + + if (mainElements.length == 0) { + return null; + } + + String name = mainElements[0]; + String offsets[] = mainElements[1].split(RANGE_SEPARATOR); + int start = 0; + int end = 1; + + if (offsets.length > 0) { + start = Integer.parseInt(offsets[0]); + end = Integer.parseInt(offsets[1]); + } + + return new Reference(ReferenceType.YIELD, name, new OffsetRange(start, end)); + } + + public static Reference extractStackDataFromIndex(String index) { + String[] mainElements = index.split(INFO_SEPARATOR); + + if (mainElements.length == 0) { + return null; + } + + String name = mainElements[0]; + String offsets[] = mainElements[1].split(RANGE_SEPARATOR); + int start = 0; + int end = 1; + + if (offsets.length > 0) { + start = Integer.parseInt(offsets[0]); + end = Integer.parseInt(offsets[1]); + } + + return new Reference(ReferenceType.STACK, name, new OffsetRange(start, end)); + } + + public static Reference extractTemplatePathDataFromIndex(String indexInfo) { + String[] mainElements = indexInfo.split(INFO_SEPARATOR); + + if (mainElements.length == 0) { + return null; + } + + String name = mainElements[0]; + String offsets[] = mainElements[1].split(RANGE_SEPARATOR); + int start = 0; + int end = 1; + + if (offsets.length > 0) { + start = Integer.parseInt(offsets[0]); + end = start + name.length(); + } + + return new Reference(ReferenceType.TEMPLATE_PATH, name, new OffsetRange(start, end)); + } + + public static String getIdFromSignature(String value) { + String[] mainElements = value.split(INFO_SEPARATOR); + if (mainElements.length == 0) { + return null; + } + + return mainElements[0]; + } + + private void storeIncludePathReferences(Map> includes, IndexDocument document) { + for (Map.Entry> entry : includes.entrySet()) { + StringBuilder sb = new StringBuilder(); + + sb.append(entry.getKey()).append(INFO_SEPARATOR); + for (OffsetRange range : entry.getValue()) { + sb.append(range.getStart()); + sb.append(RANGE_SEPARATOR); + } + + document.addPair(INCLUDE_PATH, sb.toString(), true, true); + } + } + + @MimeRegistration(mimeType = BladeLanguage.MIME_TYPE, service = EmbeddingIndexerFactory.class, position = 500) //NOI18N + public static class Factory extends EmbeddingIndexerFactory { + + public static final String NAME = "blade"; //NOI18N + public static final int VERSION = 3; + + @Override + public EmbeddingIndexer createIndexer(Indexable indxbl, Snapshot snapshot) { + if (isIndexable(snapshot)) { + return new BladeIndexer(); + } else { + return null; + } + } + + @Override + public void filesDeleted(Iterable deleted, Context context) { + try { + IndexingSupport is = IndexingSupport.getInstance(context); + for (Indexable i : deleted) { + is.removeDocuments(i); + } + } catch (IOException ioe) { + LOGGER.log(Level.WARNING, null, ioe); + } + } + + @Override + public void filesDirty(Iterable dirty, org.netbeans.modules.parsing.spi.indexing.Context context) { + try { + IndexingSupport is = IndexingSupport.getInstance(context); + for (Indexable i : dirty) { + is.markDirtyDocuments(i); + } + } catch (IOException ioe) { + LOGGER.log(Level.WARNING, null, ioe); + } + } + + @Override + public String getIndexerName() { + return NAME; + } + + @Override + public int getIndexVersion() { + return VERSION; + } + + private boolean isIndexable(Snapshot snapshot) { + return BladeLanguage.MIME_TYPE.equals(snapshot.getMimeType()); + } + } + +} diff --git a/php/php.blade/src/org/netbeans/modules/php/blade/editor/indexing/IndexManager.java b/php/php.blade/src/org/netbeans/modules/php/blade/editor/indexing/IndexManager.java new file mode 100644 index 000000000000..276562426772 --- /dev/null +++ b/php/php.blade/src/org/netbeans/modules/php/blade/editor/indexing/IndexManager.java @@ -0,0 +1,84 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.php.blade.editor.indexing; + +import java.io.File; +import java.util.Enumeration; +import org.netbeans.api.project.Project; +import org.netbeans.modules.parsing.api.indexing.IndexingManager; +import org.netbeans.modules.php.blade.project.BladeProjectProperties; +import org.openide.filesystems.FileObject; +import org.openide.filesystems.FileUtil; + +/** + * + * @author bogdan + */ +public class IndexManager { + + public static void reindexProjectViews(Project project) { + assert project != null; + String[] views = BladeProjectProperties.getInstance(project).getViewsFolderPathList(); + + if (views.length > 0) { + for (String view : views) { + if (view.length() == 0) { + continue; + } + File viewPath = new File(view); + if (viewPath.exists()) { + FileObject fileObj = FileUtil.toFileObject(viewPath); + Enumeration children = fileObj.getChildren(true); + while (children.hasMoreElements()) { + FileObject file = children.nextElement(); + if (file.isFolder()) { + continue; + } + IndexingManager.getDefault().refreshAllIndices(file); + } + } + } + } else { + //falback + String projectDir = project.getProjectDirectory().getPath(); + File viewPath = new File(projectDir + "/views"); // NOI18N + if (viewPath.exists()) { + FileObject fileObj = FileUtil.toFileObject(viewPath); + Enumeration children = fileObj.getChildren(true); + while (children.hasMoreElements()) { + FileObject file = children.nextElement(); + IndexingManager.getDefault().refreshAllIndices(file); + } + } + } + } + + public static void reindexFolder(File viewPath) { + FileObject fileObj = FileUtil.toFileObject(viewPath); + Enumeration children = fileObj.getChildren(true); + while (children.hasMoreElements()) { + FileObject file = children.nextElement(); + if (file.isFolder()) { + continue; + } + IndexingManager.getDefault().refreshAllIndices(file); + } + } + +} diff --git a/php/php.blade/src/org/netbeans/modules/php/blade/editor/indexing/PhpIndexFunctionResult.java b/php/php.blade/src/org/netbeans/modules/php/blade/editor/indexing/PhpIndexFunctionResult.java new file mode 100644 index 000000000000..439b3bad1f33 --- /dev/null +++ b/php/php.blade/src/org/netbeans/modules/php/blade/editor/indexing/PhpIndexFunctionResult.java @@ -0,0 +1,56 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.php.blade.editor.indexing; + +import java.util.List; +import org.netbeans.modules.csl.api.OffsetRange; +import org.openide.filesystems.FileObject; + +/** + * + * @author bogdan + */ +public class PhpIndexFunctionResult extends PhpIndexResult { + + protected final List params; + protected final String classNamespace; + + public PhpIndexFunctionResult(String name, FileObject fo, + PhpIndexFunctionResult.Type type, + OffsetRange range, String classNamespace, List params) { + super(name, fo, type, range); + this.params = params; + this.classNamespace = classNamespace; + } + + public String getParamsAsString() { + if (params == null || params.isEmpty()){ + return "()"; + } + return "(" + String.join(", ", params) + ")"; + } + + public List getParams(){ + return params; + } + + public String getClassNamespace(){ + return classNamespace; + } +} diff --git a/php/php.blade/src/org/netbeans/modules/php/blade/editor/indexing/PhpIndexResult.java b/php/php.blade/src/org/netbeans/modules/php/blade/editor/indexing/PhpIndexResult.java new file mode 100644 index 000000000000..7db56cacdf88 --- /dev/null +++ b/php/php.blade/src/org/netbeans/modules/php/blade/editor/indexing/PhpIndexResult.java @@ -0,0 +1,64 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.php.blade.editor.indexing; + +import org.netbeans.modules.csl.api.OffsetRange; +import org.openide.filesystems.FileObject; + +/** + * + * @author bogdan + */ +public class PhpIndexResult { + public static enum Type{ + CLASS, + FUNCTION, + NAMESPACE, + CONSTANT + }; + + public String name; + public String namespace; + public FileObject declarationFile; + public PhpIndexResult.Type type; + public OffsetRange range; + + public PhpIndexResult(String name, FileObject fo, + PhpIndexResult.Type type, + OffsetRange range){ + this.name = name; + this.declarationFile = fo; + this.type = type; + this.range = range; + } + + public PhpIndexResult(String name, String qualifiedName, FileObject fo, + PhpIndexResult.Type type, + OffsetRange range){ + this.name = name; + this.namespace = qualifiedName; + this.declarationFile = fo; + this.type = type; + this.range = range; + } + + public int getStartOffset(){ + return this.range.getStart(); + } +} diff --git a/php/php.blade/src/org/netbeans/modules/php/blade/editor/indexing/PhpIndexUtils.java b/php/php.blade/src/org/netbeans/modules/php/blade/editor/indexing/PhpIndexUtils.java new file mode 100644 index 000000000000..1821ad404f8f --- /dev/null +++ b/php/php.blade/src/org/netbeans/modules/php/blade/editor/indexing/PhpIndexUtils.java @@ -0,0 +1,785 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.php.blade.editor.indexing; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.WeakHashMap; +import org.netbeans.api.project.Project; +import org.netbeans.modules.csl.api.OffsetRange; +import org.netbeans.modules.parsing.spi.indexing.support.IndexResult; +import org.netbeans.modules.parsing.spi.indexing.support.QuerySupport; +import static org.netbeans.modules.php.blade.editor.EditorStringUtils.NAMESPACE_SEPARATOR; +import org.netbeans.modules.php.blade.editor.cache.QueryCache; +import org.netbeans.modules.php.editor.api.QuerySupportFactory; +import org.netbeans.modules.php.editor.index.PHPIndexer; +import org.netbeans.modules.php.editor.index.Signature; +import org.netbeans.spi.project.ui.support.ProjectConvertors; +import org.openide.filesystems.FileObject; +import org.openide.util.Exceptions; + +/** + * TODO needs simplification & refactor + * + * @author bhaidu + */ +public class PhpIndexUtils { + + public final static String ESCAPED_NAMESPACE_SEPARATOR = "\\\\"; // NOI18N + private final static QueryCache> cache = new QueryCache(); + private final static QueryCache> functionCache = new QueryCache(); + private final static int SIGN_NAME_POS = 1; + private final static int SIGN_METHOD_OFFSET_POS = 2; + private final static int SIGN_METHOD_PARAMS_POS = 3; + private final static int SIGN_NAMESPACE_ROOT_POS = 2; + private final static int SIGN_CLASS_NAMESPACE_POS = 4; + private final static String PHP_INDEX_INFO_SEPARATOR = ";"; + + private final static int MIN_NAMESPACE_LENGTH = 3; + + private static final Map QUERY_SUPPORT_INSTANCES = new WeakHashMap<>(); + + public enum FieldAccessType { + STATIC, + DIRECT + } + + private PhpIndexUtils() { + + } + + /** + * class query without namespace + * + * @param fo + * @param prefix + * @return + */ + public static Collection queryClass(FileObject fo, String prefix) { + QuerySupport phpindex = QuerySupportFactory.get(fo); + + QueryCache> selfCache = getCache(fo, prefix); + if (selfCache != null && selfCache.containsKey(prefix)) { + return selfCache.get(prefix).get(); + } + Collection results = new ArrayList<>(); + String queryPrefix = prefix.toLowerCase(); + try { + Collection indexResults = phpindex.query(PHPIndexer.FIELD_CLASS, + queryPrefix, QuerySupport.Kind.PREFIX, new String[]{ + PHPIndexer.FIELD_CLASS,}); + for (IndexResult indexResult : indexResults) { + FileObject indexFile = indexResult.getFile(); + + String[] values = indexResult.getValues(PHPIndexer.FIELD_CLASS); + for (String value : values) { + Signature sig = Signature.get(value); + String fullName = sig.string(SIGN_NAME_POS); + String namespace = sig.string(SIGN_CLASS_NAMESPACE_POS); + + if (fullName.length() > 0 && fullName.startsWith(prefix)) { + results.add(new PhpIndexResult(fullName, namespace, + indexFile, + PhpIndexResult.Type.CLASS, new OffsetRange(0, 1))); + } + } + } + } catch (IOException ex) { + Exceptions.printStackTrace(ex); + } + + if (selfCache != null && !results.isEmpty()) { + selfCache.put(prefix, results); + } + return results; + } + + public static Collection queryNamespaceClassesName(FileObject fo, String prefix, + String namespace) { + QuerySupport phpindex = QuerySupportFactory.get(fo); + Collection results = new ArrayList<>(); + String queryPrefix = prefix.toLowerCase() + ".*" + namespace.replace(NAMESPACE_SEPARATOR, ESCAPED_NAMESPACE_SEPARATOR) + ";.*";// NOI18N + + try { + Collection indexResults = phpindex.query( + PHPIndexer.FIELD_CLASS, queryPrefix, QuerySupport.Kind.REGEXP, new String[]{ + PHPIndexer.FIELD_CLASS, PHPIndexer.FIELD_FIELD}); + for (IndexResult indexResult : indexResults) { + FileObject indexFile = indexResult.getFile(); + + String[] values = indexResult.getValues(PHPIndexer.FIELD_CLASS); + + for (String value : values) { + Signature sig = Signature.get(value); + String fullName = sig.string(SIGN_NAME_POS); + String classNamespace = sig.string(SIGN_CLASS_NAMESPACE_POS); + if (fullName.length() > 0 + && classNamespace.length() > 0 + && classNamespace.startsWith(namespace)) { + results.add(new PhpIndexResult(fullName, + classNamespace + NAMESPACE_SEPARATOR + fullName, indexFile, PhpIndexResult.Type.CLASS, new OffsetRange(0, 1))); + } + } + } + } catch (IOException ex) { + Exceptions.printStackTrace(ex); + } + return results; + } + + public static Collection queryExactNamespaceClasses(String identifier, + String namespace, FileObject fo) { + QuerySupport phpindex = QuerySupportFactory.get(fo); + Collection results = new ArrayList<>(); + + try { + Collection indexResults = phpindex.query( + PHPIndexer.FIELD_TOP_LEVEL, namespace.toLowerCase(), QuerySupport.Kind.EXACT, + new String[]{ + PHPIndexer.FIELD_NAMESPACE + }); + for (IndexResult indexResult : indexResults) { + FileObject indexFile = indexResult.getFile(); + + if (!indexFile.getName().equals(identifier)){ + continue; + } + + String namespaceValue = indexResult.getValue(PHPIndexer.FIELD_NAMESPACE); + + if (namespaceValue == null){ + continue; + } + + results.add(new PhpIndexResult(namespace + NAMESPACE_SEPARATOR + identifier, indexFile, PhpIndexResult.Type.CLASS, new OffsetRange(0, 1))); + } + } catch (IOException ex) { + Exceptions.printStackTrace(ex); + } + return results; + } + + public static Collection queryComponentClass(String identifier, + String namespace, FileObject fo) { + QuerySupport phpindex = QuerySupportFactory.get(fo); + Collection results = new ArrayList<>(); + + try { + Collection indexResults = phpindex.query( + PHPIndexer.FIELD_TOP_LEVEL, namespace.toLowerCase(), QuerySupport.Kind.PREFIX, + new String[]{ + PHPIndexer.FIELD_NAMESPACE + }); + for (IndexResult indexResult : indexResults) { + String namespaceValue = indexResult.getValue(PHPIndexer.FIELD_NAMESPACE); + if (namespaceValue == null){ + continue; + } + Signature sig = Signature.get(namespaceValue); + String name = sig.string(SIGN_NAME_POS); + String domainName = sig.string(SIGN_NAMESPACE_ROOT_POS); + FileObject indexFile = indexResult.getFile(); + if (indexFile.getName().equals(identifier)) { + results.add(new PhpIndexResult(domainName + NAMESPACE_SEPARATOR + name + NAMESPACE_SEPARATOR + indexFile.getName(), + indexFile, PhpIndexResult.Type.CLASS, new OffsetRange(0, 1))); + } + } + } catch (IOException ex) { + Exceptions.printStackTrace(ex); + } + return results; + } + + public static Collection queryExactClass(String identifier, FileObject fo) { + QuerySupport phpindex = QuerySupportFactory.get(fo); + QueryCache> selfCache = getCache(fo, identifier); + if (selfCache != null && selfCache.containsKey(identifier)) { + return selfCache.get(identifier).get(); + } + Collection results = new ArrayList<>(); + String queryPrefix = identifier.toLowerCase(); + try { + Collection indexResults = phpindex.query(PHPIndexer.FIELD_CLASS, + queryPrefix, QuerySupport.Kind.PREFIX, new String[]{PHPIndexer.FIELD_CLASS}); + for (IndexResult indexResult : indexResults) { + FileObject indexFile = indexResult.getFile(); + + String[] values = indexResult.getValues(PHPIndexer.FIELD_CLASS); + for (String value : values) { + Signature sig = Signature.get(value); + String name = sig.string(SIGN_NAME_POS); + if (name.length() > 0 && name.equals(identifier)) { + results.add(new PhpIndexResult(name, indexFile, PhpIndexResult.Type.CLASS, new OffsetRange(0, 1))); + } + } + } + } catch (IOException ex) { + Exceptions.printStackTrace(ex); + } + if (selfCache != null && !results.isEmpty()) { + selfCache.put(identifier, results); + } + return results; + } + + public static Collection queryFunctions(FileObject fo, String prefix) { + QuerySupport phpindex = QuerySupportFactory.get(fo); + Collection results = new ArrayList<>(); + String queryPrefix = prefix.toLowerCase(); + try { + Collection indexResults = phpindex.query(PHPIndexer.FIELD_BASE, queryPrefix, QuerySupport.Kind.PREFIX, new String[]{PHPIndexer.FIELD_BASE}); + for (IndexResult indexResult : indexResults) { + FileObject indexFile = indexResult.getFile(); + //internal php index + + String[] values = indexResult.getValues(PHPIndexer.FIELD_BASE); + for (String value : values) { + Signature sig = Signature.get(value); + String name = sig.string(SIGN_NAME_POS); + + if (name.length() > 0 && name.startsWith(prefix)) { + Integer offset = sig.integer(SIGN_METHOD_OFFSET_POS); + String params = sig.string(SIGN_METHOD_PARAMS_POS); + results.add(new PhpIndexFunctionResult( + name, indexFile, + PhpIndexResult.Type.FUNCTION, + new OffsetRange(offset, offset + name.length()), + null, + parseParameters(params) + )); + } + } + } + } catch (IOException ex) { + Exceptions.printStackTrace(ex); + } + return results; + } + + public static Collection queryExactFunctions(FileObject fo, String prefix) { + QuerySupport phpindex = QuerySupportFactory.get(fo); + Collection results = new ArrayList<>(); + QueryCache> selfCache = getFunctionCache(fo, prefix); + if (selfCache != null && selfCache.containsKey(prefix)) { + return selfCache.get(prefix).get(); + } + String queryPrefix = prefix.toLowerCase(); + try { + Collection indexResults = phpindex.query(PHPIndexer.FIELD_BASE, queryPrefix, QuerySupport.Kind.PREFIX, new String[]{PHPIndexer.FIELD_BASE}); + for (IndexResult indexResult : indexResults) { + FileObject indexFile = indexResult.getFile(); + //internal php index + + String[] values = indexResult.getValues(PHPIndexer.FIELD_BASE); + for (String value : values) { + Signature sig = Signature.get(value); + String name = sig.string(1); + + if (name.length() > 0 && name.equals(prefix)) { + Integer offset = sig.integer(SIGN_METHOD_OFFSET_POS); + String params = sig.string(SIGN_METHOD_PARAMS_POS); + results.add(new PhpIndexFunctionResult(name, + indexFile, PhpIndexResult.Type.FUNCTION, + new OffsetRange(offset, offset + name.length()), + null, + parseParameters(params) + )); + } + } + } + } catch (IOException ex) { + Exceptions.printStackTrace(ex); + } + if (selfCache != null && !results.isEmpty()) { + selfCache.put(prefix, results); + } + return results; + } + + public static Collection queryExactClassMethods(FileObject fo, + String method, String className, String queryNamespace) { + QuerySupport phpindex = QuerySupportFactory.get(fo); + Collection results = new ArrayList<>(); + + String regexQuery = className.toLowerCase(); + try { + Collection indexResults = phpindex.query(PHPIndexer.FIELD_CLASS, regexQuery, + QuerySupport.Kind.PREFIX, new String[]{PHPIndexer.FIELD_CLASS, PHPIndexer.FIELD_METHOD}); + for (IndexResult indexResult : indexResults) { + FileObject indexFile = indexResult.getFile(); + //internal php index + String[] classValues = indexResult.getValues(PHPIndexer.FIELD_CLASS); + + Signature classSignature = null; + String classNamespace = null; + + for (String classValue : classValues) { + Signature sig = Signature.get(classValue); + String name = sig.string(SIGN_NAME_POS); + String namespace = sig.string(SIGN_CLASS_NAMESPACE_POS); + if (name.length() > 0 && name.equals(className) + ) { + if (queryNamespace != null && !namespace.equals(queryNamespace)){ + continue; + } + classSignature = sig; + + if (namespace.length() > 0){ + classNamespace = namespace + NAMESPACE_SEPARATOR + className; + } + } + } + + if (classSignature == null){ + continue; + } + + String[] values = indexResult.getValues(PHPIndexer.FIELD_METHOD); + for (String value : values) { + Signature sig = Signature.get(value); + String name = sig.string(SIGN_NAME_POS); + + if (name.length() > 0 && name.equals(method)) { + Integer offset = sig.integer(SIGN_METHOD_OFFSET_POS); + String params = sig.string(SIGN_METHOD_PARAMS_POS); + results.add(new PhpIndexFunctionResult(name, + indexFile, PhpIndexResult.Type.FUNCTION, + new OffsetRange(offset, offset + name.length()), + classNamespace, + parseParameters(params) + )); + } + } + } + } catch (IOException ex) { + Exceptions.printStackTrace(ex); + } + return results; + } + + /** + * + * + * @param fo + * @param method + * @param className + * @param queryNamespace + * @return + */ + public static Collection queryClassMethods(FileObject fo, + String method, String className, String queryNamespace, FieldAccessType accessType) { + QuerySupport phpindex = QuerySupportFactory.get(fo); + Collection results = new ArrayList<>(); + + if (queryNamespace != null && queryNamespace.length() > MIN_NAMESPACE_LENGTH){ + int startOffset = queryNamespace.startsWith(NAMESPACE_SEPARATOR) ? 1 : 0; + int endOffset = queryNamespace.endsWith(NAMESPACE_SEPARATOR) ? 1 : 0; + queryNamespace = queryNamespace.substring(startOffset, queryNamespace.length() - endOffset); + } + //should query the class befoe + //for the moment a quick hack + //maybe send the classNamePath directly? + String regexQuery = className.toLowerCase(); + try { + Collection indexResults = phpindex.query(PHPIndexer.FIELD_CLASS, regexQuery, + QuerySupport.Kind.PREFIX, new String[]{PHPIndexer.FIELD_CLASS, PHPIndexer.FIELD_METHOD}); + for (IndexResult indexResult : indexResults) { + FileObject indexFile = indexResult.getFile(); + String[] classValues = indexResult.getValues(PHPIndexer.FIELD_CLASS); + + Signature classSignature = null; + String classNamespace = null; + + for (String classValue : classValues) { + Signature sig = Signature.get(classValue); + String name = sig.string(SIGN_NAME_POS); + if (name.length() > 0 && name.equals(className)) { + classSignature = sig; + String namespace = sig.string(SIGN_CLASS_NAMESPACE_POS); + + if (queryNamespace != null && !namespace.equals(queryNamespace) ){ + classSignature = null; + continue; + } + + if (namespace.length() > 0){ + classNamespace = namespace + NAMESPACE_SEPARATOR + className; + } + } + } + + if (classSignature == null){ + continue; + } + + String[] values = indexResult.getValues(PHPIndexer.FIELD_METHOD); + for (String value : values) { + Signature sig = Signature.get(value); + String name = sig.string(SIGN_NAME_POS); + + Integer funcAccessType = sig.integer(5); + //todo find where does the value 9 come from + if (accessType.equals(FieldAccessType.STATIC) && funcAccessType != 9){ + //only public static methods + continue; + } + + if (name.length() > 0 && name.startsWith(method)) { + Integer offset = sig.integer(SIGN_METHOD_OFFSET_POS); + String params = sig.string(SIGN_METHOD_PARAMS_POS); + results.add(new PhpIndexFunctionResult(name, + indexFile, PhpIndexResult.Type.FUNCTION, + new OffsetRange(offset, offset + name.length()), + classNamespace, + parseParameters(params) + )); + } + } + } + } catch (IOException ex) { + Exceptions.printStackTrace(ex); + } + return results; + } + + public static Collection queryConstants(FileObject fo, String prefix) { + QuerySupport phpindex = QuerySupportFactory.get(fo); + Collection results = new ArrayList<>(); + String queryPrefix = prefix.toLowerCase(); + try { + Collection indexResults = phpindex.query(PHPIndexer.FIELD_CONST, queryPrefix, QuerySupport.Kind.PREFIX, new String[]{PHPIndexer.FIELD_CONST}); + for (IndexResult indexResult : indexResults) { + FileObject indexFile = indexResult.getFile(); + //internal php index + + String[] values = indexResult.getValues(PHPIndexer.FIELD_CONST); + for (String value : values) { + Signature sig = Signature.get(value); + String name = sig.string(SIGN_NAME_POS); + + if (name.length() > 0 && name.startsWith(prefix)) { + Integer offset = sig.integer(SIGN_METHOD_OFFSET_POS); + results.add(new PhpIndexResult(name, indexFile, PhpIndexResult.Type.CONSTANT, new OffsetRange(offset, offset + name.length()))); + } + } + } + } catch (IOException ex) { + Exceptions.printStackTrace(ex); + } + return results; + } + + public static Collection queryNamespace(FileObject fo, String prefix) { + QuerySupport phpindex = QuerySupportFactory.get(fo); + Collection results = new ArrayList<>(); + Collection namespaces = new ArrayList<>(); + //subfolders with lowercase ; rootFolder + //third signature namespace + //the first el is the folder + String originalPrefix = prefix; + + if (prefix.endsWith(ESCAPED_NAMESPACE_SEPARATOR)) { + return results; + } + + String[] queryItems = prefix.split(ESCAPED_NAMESPACE_SEPARATOR); + + if (queryItems.length == 0) { + return results; + } + + String queryPrefix = prefix.toLowerCase(); + + try { + Collection indexResults = phpindex.query( + PHPIndexer.FIELD_TOP_LEVEL, queryPrefix, QuerySupport.Kind.PREFIX, new String[]{ + PHPIndexer.FIELD_NAMESPACE, PHPIndexer.FIELD_TOP_LEVEL}); + for (IndexResult indexResult : indexResults) { + FileObject indexFile = indexResult.getFile(); + String topFieldValue = indexResult.getValue(PHPIndexer.FIELD_TOP_LEVEL); + //internal php index + if (topFieldValue.startsWith(prefix.toLowerCase())) { + String firstValue = indexResult.getValue(PHPIndexer.FIELD_NAMESPACE); + if (firstValue == null || firstValue.isEmpty()) { + continue; + } + Signature sig = Signature.get(firstValue); + + String name = sig.string(SIGN_NAME_POS); + String namespace = sig.string(SIGN_NAMESPACE_ROOT_POS); + + String fullNamespace = ""; // NOI18N + + if (!namespace.isEmpty()) { + fullNamespace = namespace + NAMESPACE_SEPARATOR; + } + + fullNamespace += name; + + //just one namespace is enough + if (fullNamespace.startsWith(originalPrefix) && !namespaces.contains(fullNamespace)) { + namespaces.add(fullNamespace); + results.add(new PhpIndexResult(fullNamespace, indexFile, PhpIndexResult.Type.NAMESPACE, new OffsetRange(0, 1))); + } + } + } + } catch (IOException ex) { + Exceptions.printStackTrace(ex); + } + return results; + } + + public static Collection queryNamespaces(FileObject fo, String namespace, + QuerySupport.Kind queryType) { + QuerySupport phpindex = QuerySupportFactory.get(fo); + Collection results = new ArrayList<>(); + String queryPrefix = namespace.toLowerCase(); + + try { + Collection indexResults = phpindex.query( + PHPIndexer.FIELD_TOP_LEVEL, queryPrefix, queryType, + new String[]{ + PHPIndexer.FIELD_NAMESPACE, + PHPIndexer.FIELD_TOP_LEVEL + }); + for (IndexResult indexResult : indexResults) { + FileObject indexFile = indexResult.getFile(); + String namespaceValue = indexResult.getValue(PHPIndexer.FIELD_NAMESPACE); + //no namespace found + if (namespaceValue == null){ + continue; + } + results.add(new PhpIndexResult(namespaceValue, indexFile, PhpIndexResult.Type.NAMESPACE, new OffsetRange(0, 1))); + } + } catch (IOException ex) { + Exceptions.printStackTrace(ex); + } + return results; + } + + /** + * a optimized hack solution + * assuming that the name of the class is the same with the file + * + * @param fo + * @param namespace + * @return + */ + public static Collection queryAllNamespaceClasses(FileObject fo, String namespace) { + QuerySupport phpindex = QuerySupportFactory.get(fo); + Collection results = new ArrayList<>(); + String queryPrefix = namespace.toLowerCase(); + + try { + Collection indexResults = phpindex.query( + PHPIndexer.FIELD_TOP_LEVEL, queryPrefix, QuerySupport.Kind.PREFIX, + new String[]{ + PHPIndexer.FIELD_NAMESPACE, + PHPIndexer.FIELD_TOP_LEVEL + }); + for (IndexResult indexResult : indexResults) { + FileObject indexFile = indexResult.getFile(); + String namespaceValue = indexResult.getValue(PHPIndexer.FIELD_NAMESPACE); + //no namespace found + if (namespaceValue == null){ + continue; + } + results.add(new PhpIndexResult(indexFile.getName(), indexFile, PhpIndexResult.Type.CLASS, new OffsetRange(0, 1))); + } + } catch (IOException ex) { + Exceptions.printStackTrace(ex); + } + return results; + } + + public static Collection queryClassConstants(FileObject fo, String prefix, String ownerClass) { + QuerySupport phpindex = QuerySupportFactory.get(fo); + Collection results = new ArrayList<>(); + String queryPrefix = prefix.toLowerCase(); + try { + Collection indexResults = phpindex.query(PHPIndexer.FIELD_CLASS_CONST, queryPrefix, QuerySupport.Kind.PREFIX, new String[]{ + PHPIndexer.FIELD_CLASS_CONST, PHPIndexer.FIELD_CLASS}); + for (IndexResult indexResult : indexResults) { + FileObject indexFile = indexResult.getFile(); + //internal php index + + String classOwnerName = indexResult.getValue(PHPIndexer.FIELD_CLASS); + if (classOwnerName == null || !classOwnerName.startsWith(ownerClass.toLowerCase())) { + continue; + } + String[] values = indexResult.getValues(PHPIndexer.FIELD_CLASS_CONST); + for (String value : values) { + Signature sig = Signature.get(value); + String name = sig.string(SIGN_NAME_POS); + + if (name.length() > 0 && name.startsWith(prefix)) { + Integer offset = sig.integer(SIGN_METHOD_OFFSET_POS); + results.add(new PhpIndexResult(name, indexFile, PhpIndexResult.Type.CONSTANT, new OffsetRange(offset, offset + name.length()))); + } + } + } + } catch (IOException ex) { + Exceptions.printStackTrace(ex); + } + return results; + } + + public static Collection queryExactClassConstants(String prefix, String ownerClass, FileObject fo) { + QuerySupport phpindex = QuerySupportFactory.get(fo); + Collection results = new ArrayList<>(); + String queryPrefix = prefix.toLowerCase(); + try { + Collection indexResults = phpindex.query(PHPIndexer.FIELD_CLASS_CONST, queryPrefix, QuerySupport.Kind.PREFIX, new String[]{ + PHPIndexer.FIELD_CLASS_CONST, PHPIndexer.FIELD_CLASS}); + for (IndexResult indexResult : indexResults) { + FileObject indexFile = indexResult.getFile(); + //internal php index + + String classOwnerName = indexResult.getValue(PHPIndexer.FIELD_CLASS); + if (!classOwnerName.startsWith(ownerClass.toLowerCase() + PHP_INDEX_INFO_SEPARATOR)) { + continue; + } + String[] values = indexResult.getValues(PHPIndexer.FIELD_CLASS_CONST); + for (String value : values) { + Signature sig = Signature.get(value); + String name = sig.string(SIGN_NAME_POS); + + if (name.length() > 0 && name.equals(prefix)) { + Integer offset = sig.integer(SIGN_METHOD_OFFSET_POS); + results.add(new PhpIndexResult(name, indexFile, PhpIndexResult.Type.CONSTANT, new OffsetRange(offset, offset + name.length()))); + } + } + } + } catch (IOException ex) { + Exceptions.printStackTrace(ex); + } + return results; + } + + public static Collection queryExactConstants(FileObject fo, String prefix) { + QuerySupport phpindex = QuerySupportFactory.get(fo); + Collection results = new ArrayList<>(); + String queryPrefix = prefix.toLowerCase(); + try { + Collection indexResults = phpindex.query(PHPIndexer.FIELD_CONST, queryPrefix, QuerySupport.Kind.PREFIX, new String[]{PHPIndexer.FIELD_CONST}); + for (IndexResult indexResult : indexResults) { + FileObject indexFile = indexResult.getFile(); + //internal php index + + String[] values = indexResult.getValues(PHPIndexer.FIELD_CONST); + for (String value : values) { + Signature sig = Signature.get(value); + String name = sig.string(SIGN_NAME_POS); + + if (name.length() > 0 && name.equals(prefix)) { + Integer offset = sig.integer(SIGN_METHOD_OFFSET_POS); + results.add(new PhpIndexResult(name, indexFile, PhpIndexResult.Type.FUNCTION, new OffsetRange(offset, offset + name.length()))); + } + } + } + } catch (IOException ex) { + Exceptions.printStackTrace(ex); + } + return results; + } + + static List parseParameters(final String signature) { + List retval = new ArrayList<>(); + if (signature != null && signature.length() > 0) { + final String regexp = String.format("\\%s", ","); //NOI18N + + for (String sign : signature.split(regexp)) { + try { + final String param = parseOneParameter(sign); + if (param != null) { + retval.add(param); + } + } catch (NumberFormatException originalException) { + final String message = String.format("%s [for signature: %s]", originalException.getMessage(), signature); //NOI18N + final NumberFormatException formatException = new NumberFormatException(message); + formatException.initCause(originalException); + throw formatException; + } + } + } + return retval; + } + + /** + * See more info on org.netbeans.modules.php.editor.model.impl.ParameterImpl for signature structure + * + * @param sig + * @return + */ + static String parseOneParameter(String sig) { + String retval = null; + final String regexp = String.format("\\%s", ":"); //NOI18N + String[] parts = sig.split(regexp); + if (parts.length > 0) { + String paramName = parts[0]; + retval = paramName; + } + return retval; + } + + protected static QueryCache> getCache(FileObject fo, String prefix) { + QueryCache> selfCache = new QueryCache<>(); + Project projectOwner = ProjectConvertors.getNonConvertorOwner(fo); + if (projectOwner == null) { + return null; + } + int pathHash = projectOwner.getProjectDirectory().toString().hashCode(); + if (PhpIndexUtils.QUERY_SUPPORT_INSTANCES.containsKey(pathHash)) { + PhpIndexUtils indexUtils = QUERY_SUPPORT_INSTANCES.get(pathHash); + selfCache = indexUtils.getQueryCache(); + + } else { + QUERY_SUPPORT_INSTANCES.put(pathHash, new PhpIndexUtils()); + } + return selfCache; + } + + protected static QueryCache> getFunctionCache(FileObject fo, String prefix) { + QueryCache> selfCache = new QueryCache<>(); + Project projectOwner = ProjectConvertors.getNonConvertorOwner(fo); + if (projectOwner == null) { + return null; + } + int pathHash = projectOwner.getProjectDirectory().toString().hashCode(); + if (PhpIndexUtils.QUERY_SUPPORT_INSTANCES.containsKey(pathHash)) { + PhpIndexUtils indexUtils = QUERY_SUPPORT_INSTANCES.get(pathHash); + selfCache = indexUtils.getFunctionQueryCache(); + + } else { + QUERY_SUPPORT_INSTANCES.put(pathHash, new PhpIndexUtils()); + } + return selfCache; + } + + public QueryCache> getQueryCache() { + return cache; + } + + public QueryCache> getFunctionQueryCache() { + return functionCache; + } +} diff --git a/php/php.blade/src/org/netbeans/modules/php/blade/editor/indexing/QueryUtils.java b/php/php.blade/src/org/netbeans/modules/php/blade/editor/indexing/QueryUtils.java new file mode 100644 index 000000000000..18e27e90c94f --- /dev/null +++ b/php/php.blade/src/org/netbeans/modules/php/blade/editor/indexing/QueryUtils.java @@ -0,0 +1,74 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.php.blade.editor.indexing; + +import java.io.IOException; +import java.util.List; +import org.netbeans.api.annotations.common.CheckForNull; +import org.netbeans.api.project.Project; +import org.netbeans.modules.php.blade.project.ProjectUtils; +import org.openide.filesystems.FileObject; +import org.openide.util.Exceptions; + +/** + * + * @author bogdan + */ +public final class QueryUtils { + + private QueryUtils() { + + } + + public static List findYieldReferences(String prefix, FileObject fo) { + BladeIndex bladeIndex = getIndex(fo); + if (bladeIndex == null) { + return List.of(); + } + return bladeIndex.findYieldIndexedReferences(prefix); + } + + public static List findStacksReferences(String prefix, FileObject fo) { + BladeIndex bladeIndex = getIndex(fo); + if (bladeIndex == null) { + return List.of(); + } + return bladeIndex.findStackIdIndexedReferences(prefix); + } + + public static List getIncludePathReferences(String prefix, FileObject fo) { + BladeIndex bladeIndex = getIndex(fo); + if (bladeIndex == null) { + return List.of(); + } + return bladeIndex.getIncludePaths(prefix); + } + + @CheckForNull + public static BladeIndex getIndex(FileObject fo) { + Project project = ProjectUtils.getMainOwner(fo); + + try { + return BladeIndex.get(project); + } catch (IOException ex) { + Exceptions.printStackTrace(ex); + } + return null; + } +} diff --git a/php/php.blade/src/org/netbeans/modules/php/blade/editor/lexer/BladeLexer.java b/php/php.blade/src/org/netbeans/modules/php/blade/editor/lexer/BladeLexer.java new file mode 100644 index 000000000000..55f9a4574459 --- /dev/null +++ b/php/php.blade/src/org/netbeans/modules/php/blade/editor/lexer/BladeLexer.java @@ -0,0 +1,88 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.php.blade.editor.lexer; + +import org.netbeans.api.lexer.Token; +import static org.netbeans.modules.php.blade.editor.lexer.BladeTokenId.*; +import org.netbeans.modules.php.blade.syntax.antlr4.v10.BladeAntlrColoringLexer; +import org.netbeans.spi.lexer.LexerRestartInfo; +import org.netbeans.spi.lexer.antlr4.AbstractAntlrLexerBridge; + +/** + * + * @author bogdan + */ +public class BladeLexer extends AbstractAntlrLexerBridge { + + public BladeLexer(LexerRestartInfo info) { + super(info, BladeAntlrColoringLexer::new); + } + + @Override + public Object state() { + return new State(lexer); + } + + @Override + protected Token mapToken(org.antlr.v4.runtime.Token antlrToken) { + + return switch (antlrToken.getType()) { + case BladeAntlrColoringLexer.HTML_TAG, BladeAntlrColoringLexer.HTML -> groupToken(HTML, BladeAntlrColoringLexer.HTML); + case BladeAntlrColoringLexer.COMPONENT_ATTR -> groupToken(BLADE_COMPONENT_ATTRIBUTE, BladeAntlrColoringLexer.COMPONENT_ATTR); + case BladeAntlrColoringLexer.DIRECTIVE, BladeAntlrColoringLexer.D_PHP, BladeAntlrColoringLexer.D_ENDPHP -> token(BLADE_DIRECTIVE); + case BladeAntlrColoringLexer.D_CUSTOM -> token(BLADE_CUSTOM_DIRECTIVE); + case BladeAntlrColoringLexer.RAW_TAG, BladeAntlrColoringLexer.CONTENT_TAG -> token(BLADE_ECHO_DELIMITOR); + case BladeAntlrColoringLexer.BLADE_PHP_ECHO_EXPR -> token(PHP_BLADE_ECHO_EXPR); + case BladeAntlrColoringLexer.D_UNKNOWN, BladeAntlrColoringLexer.D_AT -> groupToken(BLADE_DIRECTIVE_UNKNOWN, BladeAntlrColoringLexer.D_UNKNOWN); + case BladeAntlrColoringLexer.BLADE_PAREN -> token(BLADE_PAREN); + case BladeAntlrColoringLexer.BLADE_COMMENT_START -> token(BLADE_COMMENT_START); + case BladeAntlrColoringLexer.BLADE_COMMENT -> groupToken(BLADE_COMMENT, BladeAntlrColoringLexer.BLADE_COMMENT); + case BladeAntlrColoringLexer.BLADE_COMMENT_END -> token(BLADE_COMMENT_END); + case BladeAntlrColoringLexer.PHP_INLINE -> token(PHP_INLINE); + case BladeAntlrColoringLexer.PHP_EXPRESSION -> groupToken(PHP_BLADE_EXPRESSION, BladeAntlrColoringLexer.PHP_EXPRESSION); + case BladeAntlrColoringLexer.BLADE_PHP_INLINE -> groupToken(PHP_BLADE_INLINE_CODE, BladeAntlrColoringLexer.BLADE_PHP_INLINE); + case BladeAntlrColoringLexer.ERROR -> token(WS_D); + default -> token(OTHER); + }; + } + + private static class State extends AbstractAntlrLexerBridge.LexerState { + + final int rParenBalance; + final int compAttrQuoteBalance; + final boolean insideComponentTag; + + public State(BladeAntlrColoringLexer lexer) { + super(lexer); + this.rParenBalance = lexer.rParenBalance; + this.compAttrQuoteBalance = lexer.compAttrQuoteBalance; + this.insideComponentTag = lexer.insideComponentTag; + } + + @Override + public void restore(BladeAntlrColoringLexer lexer) { + super.restore(lexer); + lexer.rParenBalance = rParenBalance; + lexer.compAttrQuoteBalance = compAttrQuoteBalance; + lexer.insideComponentTag = insideComponentTag; + } + + } + +} diff --git a/php/php.blade/src/org/netbeans/modules/php/blade/editor/lexer/BladeLexerUtils.java b/php/php.blade/src/org/netbeans/modules/php/blade/editor/lexer/BladeLexerUtils.java new file mode 100644 index 000000000000..6ba9eea6d89d --- /dev/null +++ b/php/php.blade/src/org/netbeans/modules/php/blade/editor/lexer/BladeLexerUtils.java @@ -0,0 +1,160 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.php.blade.editor.lexer; + +import java.util.List; +import javax.swing.text.Document; +import org.netbeans.api.html.lexer.HTMLTokenId; +import org.netbeans.api.lexer.Language; +import org.netbeans.api.lexer.Token; +import org.netbeans.api.lexer.TokenHierarchy; +import org.netbeans.api.lexer.TokenSequence; +import org.netbeans.editor.BaseDocument; +import org.netbeans.modules.php.blade.editor.BladeLanguage; +import org.netbeans.modules.php.editor.lexer.PHPTokenId; + +/** + * + * @author bogdan + */ +public final class BladeLexerUtils { + + private BladeLexerUtils() { + + } + + public static TokenSequence getLockedPhpTokenSequence(Document doc, int offset) { + BaseDocument baseDoc = (BaseDocument) doc; + TokenSequence tokenSequence = null; + baseDoc.readLock(); + try { + TokenHierarchy hierarchy = TokenHierarchy.get(baseDoc); + tokenSequence = hierarchy.tokenSequence(PHPTokenId.language()); + } finally { + baseDoc.readUnlock(); + } + if (tokenSequence != null) { + tokenSequence.move(offset); + tokenSequence.moveNext(); + } + return tokenSequence; + + } + + public static TokenSequence getPhpTokenSequence(TokenHierarchy th, final int offset) { + return getTokenSequence(th, offset, PHPTokenId.language()); + } + + + public static TokenSequence getPhpTokenSequence(final Document document, final int offset) { + TokenHierarchy th = TokenHierarchy.get(document); + return getTokenSequence(th, offset, PHPTokenId.language()); + } + + public static TokenSequence getHtmlTokenSequence(TokenHierarchy th, final int offset) { + return getTokenSequence(th, offset, HTMLTokenId.language()); + } + + public static TokenSequence getHtmlTokenSequence(final Document document, final int offset) { + TokenHierarchy th = TokenHierarchy.get(document); + return getTokenSequence(th, offset, HTMLTokenId.language()); + } + + public static Token getHtmlToken(TokenHierarchy th, final int offset) { + TokenSequence tsHtml = BladeLexerUtils.getHtmlTokenSequence(th, offset); + if (tsHtml == null) { + return null; + } + tsHtml.move(offset); + + if (!tsHtml.moveNext() && !tsHtml.movePrevious()) { + return null; + } + + return tsHtml.token(); + } + + public static TokenSequence getTokenSequence(final Document document, final int offset) { + return getBladeTokenSequence(TokenHierarchy.get(document), offset); + } + + public static TokenSequence getBladeTokenSequence(TokenHierarchy th, int offset) { + BladeLanguage lang = new BladeLanguage(); + TokenSequence ts = th.tokenSequence(lang.getLexerLanguage()); + + return ts; + } + + public static TokenSequence getBladeTokenSequenceDoc(TokenHierarchy th, int offset) { + BladeLanguage lang = new BladeLanguage(); + TokenSequence ts = th.tokenSequence(lang.getLexerLanguage()); + + return ts; + } + + public static Token getBladeToken(final Document document, final int offset){ + TokenSequence ts = getTokenSequence(document, offset); + return getBladeToken(ts, offset); + } + + public static Token getBladeToken(TokenHierarchy th, final int offset){ + TokenSequence ts = getBladeTokenSequence(th, offset); + return getBladeToken(ts, offset); + } + + public static Token getBladeToken(TokenSequence ts, final int offset){ + if (ts == null){ + return null; + } + + ts.move(offset); + + if (!ts.moveNext() && !ts.movePrevious()) { + return null; + } + + return ts.token(); + } + + @SuppressWarnings({"unchecked", "rawtypes"}) + public static TokenSequence getTokenSequence(final TokenHierarchy th, + final int offset, final Language language) { + TokenSequence ts = th.tokenSequence(language); + if (ts == null) { + List> list = th.embeddedTokenSequences(offset, true); + for (TokenSequence t : list) { + if (t.language() == language) { + ts = t; + break; + } + } + if (ts == null) { + list = th.embeddedTokenSequences(offset, false); + for (TokenSequence t : list) { + if (t.language() == language) { + ts = t; + break; + } + } + } + } + return ts; + } + +} diff --git a/php/php.blade/src/org/netbeans/modules/php/blade/editor/lexer/BladeTokenId.java b/php/php.blade/src/org/netbeans/modules/php/blade/editor/lexer/BladeTokenId.java new file mode 100644 index 000000000000..54674dc957c4 --- /dev/null +++ b/php/php.blade/src/org/netbeans/modules/php/blade/editor/lexer/BladeTokenId.java @@ -0,0 +1,103 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.php.blade.editor.lexer; + +import java.util.Collection; +import java.util.EnumSet; +import org.netbeans.api.html.lexer.HTMLTokenId; +import org.netbeans.api.lexer.InputAttributes; +import org.netbeans.api.lexer.Language; +import org.netbeans.api.lexer.LanguagePath; +import org.netbeans.api.lexer.Token; +import org.netbeans.api.lexer.TokenId; +import org.netbeans.spi.lexer.LanguageEmbedding; +import org.netbeans.spi.lexer.LanguageHierarchy; +import org.netbeans.modules.php.editor.lexer.PHPTokenId; + +/** + * + * @author bogdan + */ +public enum BladeTokenId implements TokenId { + BLADE_COMMENT_START("blade_comment"), // NOI18N + BLADE_COMMENT("blade_comment"), // NOI18N + BLADE_COMMENT_END("blade_comment"), // NOI18N + BLADE_DIRECTIVE("blade_directive"), // NOI18N + BLADE_CUSTOM_DIRECTIVE("blade_directive"), // NOI18N + BLADE_ECHO_DELIMITOR("blade_echo_delimiters"), // NOI18N + BLADE_TAG_ERROR("html"), // NOI18N + BLADE_PAREN("token"), // NOI18N + BLADE_COMPONENT_ATTRIBUTE("blade_comp_attribute"), // NOI18N + HTML("html"), // NOI18N + WS_D("html"), // NOI18N + BLADE_DIRECTIVE_UNKNOWN("at_string"), // NOI18N + PHP_BLADE_EXPRESSION("blade_php"), // NOI18N + PHP_BLADE_ECHO_EXPR("blade_php"), // NOI18N + PHP_BLADE_INLINE_CODE("blade_php"), // NOI18N + PHP_INLINE("php"), // NOI18N + OTHER("error"); // NOI18N + private final String category; + + BladeTokenId(String category) { + this.category = category; + } + + @Override + public String primaryCategory() { + return category; + } + + public static abstract class BladeLanguageHierarchy extends LanguageHierarchy { + + @Override + protected Collection createTokenIds() { + return EnumSet.allOf(BladeTokenId.class); + } + + @Override + protected LanguageEmbedding embedding(Token token, + LanguagePath languagePath, InputAttributes inputAttributes) { + + if (token.text() == null) { + return null; + } + + switch (token.id()) { + case PHP_BLADE_EXPRESSION: + case PHP_BLADE_INLINE_CODE: + case PHP_BLADE_ECHO_EXPR: { + //php code without wrapping tags + Language langInPhp = PHPTokenId.languageInPHP(); + return langInPhp != null ? LanguageEmbedding.create(langInPhp, 0, 0, false) : null; + } + case PHP_INLINE: { + //generic inline ?php ... ?> wrapping tags + Language phpLanguageCode = PHPTokenId.language(); + return phpLanguageCode != null ? LanguageEmbedding.create(phpLanguageCode, 0, 0, false) : null; + } + case HTML: { + return LanguageEmbedding.create(HTMLTokenId.language(), 0, 0, true); + } + default: { + return null; + } + } + } + } +} diff --git a/php/php.blade/src/org/netbeans/modules/php/blade/editor/navigator/BladeStructureItem.java b/php/php.blade/src/org/netbeans/modules/php/blade/editor/navigator/BladeStructureItem.java new file mode 100644 index 000000000000..0e14ecc5e01f --- /dev/null +++ b/php/php.blade/src/org/netbeans/modules/php/blade/editor/navigator/BladeStructureItem.java @@ -0,0 +1,218 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.php.blade.editor.navigator; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Set; +import javax.swing.ImageIcon; +import org.netbeans.modules.csl.api.ElementHandle; +import org.netbeans.modules.csl.api.ElementKind; +import org.netbeans.modules.csl.api.HtmlFormatter; +import org.netbeans.modules.csl.api.Modifier; +import org.netbeans.modules.csl.api.OffsetRange; +import org.netbeans.modules.csl.api.StructureItem; +import org.netbeans.modules.csl.spi.ParserResult; +import org.netbeans.modules.php.blade.editor.ResourceUtilities; +import org.openide.filesystems.FileObject; + +/** + * + * @author bhaidu + */ +public abstract class BladeStructureItem implements ElementHandle, StructureItem { + + final String name; + final FileObject source; + final int startOffset; + final int stopOffset; + + public BladeStructureItem(String name, FileObject source, int startOffset, int stopOffset) { + this.name = name; + this.source = source; + this.startOffset = startOffset; + this.stopOffset = stopOffset; + } + + @Override + public String getSortText() { + return String.format("[%8d]", this.startOffset).replace(' ', '0'); + } + + @Override + public String getHtml(HtmlFormatter formatter) { + formatter.appendText(name); + return formatter.getText(); + } + + @Override + public ElementHandle getElementHandle() { + return this; + } + + @Override + public long getPosition() { + return startOffset; + } + + @Override + public long getEndPosition() { + return stopOffset; + } + + @Override + public ImageIcon getCustomIcon() { + return null; + } + + @Override + public FileObject getFileObject() { + return source; + } + + @Override + public String getMimeType() { + return source.getMIMEType(); + } + + @Override + public String getName() { + return name; + } + + @Override + public String getIn() { + return null; + } + + @Override + public Set getModifiers() { + return Collections.emptySet(); + } + + @Override + public boolean signatureEquals(ElementHandle handle) { + return false; + } + + @Override + public OffsetRange getOffsetRange(ParserResult result) { + return new OffsetRange(startOffset, stopOffset); + } + + public static final class DirectiveBlockStructureItem extends BladeStructureItem { + + private String identifier; + + public final List nestedItems = new ArrayList<>(); + + public DirectiveBlockStructureItem(String name, FileObject source, int startOffset, int stopOffset) { + super(name, source, startOffset, stopOffset); + } + + public DirectiveBlockStructureItem(String name, String identifier, FileObject source, int startOffset, int stopOffset) { + super(name, source, startOffset, stopOffset); + this.identifier = identifier; + } + + @Override + public boolean isLeaf() { + return nestedItems.isEmpty(); + } + + @Override + public List getNestedItems() { + return nestedItems; + } + + @Override + public ElementKind getKind() { + return ElementKind.CLASS; + } + + @Override + public String getHtml(HtmlFormatter formatter) { + formatter.appendText(name); + if (identifier != null) { + formatter.appendText(" "); + formatter.appendHtml(""); + formatter.appendHtml(""); + formatter.appendText(identifier); + formatter.appendHtml(""); + formatter.appendHtml(""); + } + return formatter.getText(); + } + + @Override + public ImageIcon getCustomIcon() { + return ResourceUtilities.loadLayoutIcon(); + } + } + + public static class DirectiveInlineStructureItem extends BladeStructureItem { + + private String identifier; + + public DirectiveInlineStructureItem(String name, FileObject source, int startOffset, int stopOffset) { + super(name, source, startOffset, stopOffset); + } + + public DirectiveInlineStructureItem(String name, String identifier, FileObject source, int startOffset, int stopOffset) { + super(name, source, startOffset, stopOffset); + this.identifier = identifier; + } + + @Override + public boolean isLeaf() { + return true; + } + + @Override + public String getHtml(HtmlFormatter formatter) { + formatter.appendText(name); + if (identifier != null) { + formatter.appendText(" "); + formatter.appendHtml(""); + formatter.appendHtml(""); + formatter.appendText(identifier); + formatter.appendHtml(""); + formatter.appendHtml(""); + } + return formatter.getText(); + } + + @Override + public List getNestedItems() { + return Collections.emptyList(); + } + + @Override + public ElementKind getKind() { + return ElementKind.CLASS; + } + + @Override + public ImageIcon getCustomIcon() { + return ResourceUtilities.loadLayoutIcon(); + } + } + +} diff --git a/php/php.blade/src/org/netbeans/modules/php/blade/editor/navigator/BladeStructureScanner.java b/php/php.blade/src/org/netbeans/modules/php/blade/editor/navigator/BladeStructureScanner.java new file mode 100644 index 000000000000..7557b8a300e0 --- /dev/null +++ b/php/php.blade/src/org/netbeans/modules/php/blade/editor/navigator/BladeStructureScanner.java @@ -0,0 +1,53 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.php.blade.editor.navigator; + +import java.util.Collections; +import java.util.List; +import java.util.Map; +import org.netbeans.modules.csl.api.OffsetRange; +import org.netbeans.modules.csl.api.StructureItem; +import org.netbeans.modules.csl.api.StructureScanner; +import org.netbeans.modules.csl.api.StructureScanner.Configuration; +import org.netbeans.modules.csl.spi.ParserResult; +import org.netbeans.modules.php.blade.editor.parser.BladeParserResult; + +/** + * + * @author bhaidu + */ +public class BladeStructureScanner implements StructureScanner { + + @Override + public List scan(ParserResult info) { + return ((BladeParserResult)info).structure; + } + + @Override + public Map> folds(ParserResult info) { + Map> ret = Collections.singletonMap("codeblocks", ((BladeParserResult)info).folds); //NOI18N + return ret; + } + + @Override + public Configuration getConfiguration() { + return new Configuration(true, false); + } + +} \ No newline at end of file diff --git a/php/php.blade/src/org/netbeans/modules/php/blade/editor/navigator/DirectiveStructureItem.java b/php/php.blade/src/org/netbeans/modules/php/blade/editor/navigator/DirectiveStructureItem.java new file mode 100644 index 000000000000..91b0416201b7 --- /dev/null +++ b/php/php.blade/src/org/netbeans/modules/php/blade/editor/navigator/DirectiveStructureItem.java @@ -0,0 +1,125 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.php.blade.editor.navigator; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import javax.swing.ImageIcon; +import org.netbeans.modules.csl.api.ElementKind; +import org.netbeans.modules.csl.api.HtmlFormatter; +import org.netbeans.modules.csl.api.StructureItem; +import org.netbeans.modules.php.blade.editor.ResourceUtilities; +import org.openide.filesystems.FileObject; + +/** + * + * @author bogdan + */ +public abstract class DirectiveStructureItem extends BladeStructureItem { + + private String identifierArg; + + public DirectiveStructureItem(String name, FileObject source, int startOffset, int stopOffset) { + super(name, source, startOffset, stopOffset); + } + + public DirectiveStructureItem(String name, String identifierArg, FileObject source, int startOffset, int stopOffset) { + super(name, source, startOffset, stopOffset); + this.identifierArg = identifierArg; + } + + public String getDirectiveIdentiferArg() { + return identifierArg; + } + + @Override + public ElementKind getKind() { + return ElementKind.CLASS; + } + + @Override + public String getHtml(HtmlFormatter formatter) { + formatter.appendText(getName()); + if (getDirectiveIdentiferArg() != null) { + formatter.appendText(" "); //NOI18N + formatter.appendHtml(""); //NOI18N + formatter.appendHtml(""); //NOI18N + formatter.appendText(getDirectiveIdentiferArg()); + formatter.appendHtml(""); //NOI18N + formatter.appendHtml(""); //NOI18N + } + return formatter.getText(); + } + + @Override + public ImageIcon getCustomIcon() { + return ResourceUtilities.loadLayoutIcon(); + } + + public static class DirectiveInlineStructureItem extends DirectiveStructureItem { + + public DirectiveInlineStructureItem(String name, String identifierArg, FileObject source, int startOffset, int stopOffset) { + super(name, identifierArg, source, startOffset, stopOffset); + } + + @Override + public boolean isLeaf() { + return true; + } + + @Override + public List getNestedItems() { + return Collections.emptyList(); + } + } + + public static final class DirectiveBlockStructureItem extends DirectiveStructureItem { + + private int depth; + public final List nestedItems = new ArrayList<>(); + + public DirectiveBlockStructureItem(String name, FileObject source, int startOffset, int stopOffset) { + super(name, source, startOffset, stopOffset); + } + + public DirectiveBlockStructureItem(String name, String identifierArg, FileObject source, int startOffset, int stopOffset) { + super(name, identifierArg, source, startOffset, stopOffset); + } + + @Override + public boolean isLeaf() { + return nestedItems.isEmpty(); + } + + @Override + public List getNestedItems() { + return nestedItems; + } + + public void setDepth(int depth) { + this.depth = depth; + } + + public int getDepth() { + return depth; + } + } + +} diff --git a/php/php.blade/src/org/netbeans/modules/php/blade/editor/parser/BladeComponentTagOccurences.java b/php/php.blade/src/org/netbeans/modules/php/blade/editor/parser/BladeComponentTagOccurences.java new file mode 100644 index 000000000000..17cbaec4e49b --- /dev/null +++ b/php/php.blade/src/org/netbeans/modules/php/blade/editor/parser/BladeComponentTagOccurences.java @@ -0,0 +1,52 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.php.blade.editor.parser; + +import java.util.Set; +import java.util.TreeSet; +import org.netbeans.modules.csl.api.OffsetRange; + +/** + * + * @author bogdan + */ +public class BladeComponentTagOccurences { + + private final Set bladeComponentTagOcurences = new TreeSet<>(); + + public void markPhpExpressionOccurence(OffsetRange range) { + bladeComponentTagOcurences.add(range); + } + + public OffsetRange findOffsetBladeComponentTag(int offset) { + for (OffsetRange range : bladeComponentTagOcurences) { + + if (offset < range.getStart()) { + //excedeed the offset range + break; + } + + if (range.containsInclusive(offset)) { + return range; + } + } + + return null; + } +} diff --git a/php/php.blade/src/org/netbeans/modules/php/blade/editor/parser/BladeCustomDirectiveOccurences.java b/php/php.blade/src/org/netbeans/modules/php/blade/editor/parser/BladeCustomDirectiveOccurences.java new file mode 100644 index 000000000000..04ba3ce945df --- /dev/null +++ b/php/php.blade/src/org/netbeans/modules/php/blade/editor/parser/BladeCustomDirectiveOccurences.java @@ -0,0 +1,66 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.php.blade.editor.parser; + +import java.util.Map; +import java.util.TreeMap; +import org.netbeans.modules.csl.api.OffsetRange; + +/** + * + * @author bogdan + */ +public class BladeCustomDirectiveOccurences { + + private final Map customDirectiveOccurences = new TreeMap<>(); + + public void markPhpExpressionOccurence(OffsetRange range, String directiveLabel) { + customDirectiveOccurences.put(range, directiveLabel); + } + + public Map getAll() { + return customDirectiveOccurences; + } + + public CustomDirectiveOccurence findCustomDirectiveOccurence(int offset) { + for (Map.Entry entry : customDirectiveOccurences.entrySet()) { + + if (offset < entry.getKey().getStart()) { + //excedeed the offset range + break; + } + + if (entry.getKey().containsInclusive(offset)) { + return new CustomDirectiveOccurence(entry.getKey(), entry.getValue()); + } + } + + return null; + } + + public static class CustomDirectiveOccurence{ + public final OffsetRange range; + public final String directiveName; + + public CustomDirectiveOccurence(OffsetRange range, String directiveName){ + this.range = range; + this.directiveName = directiveName; + } + } +} diff --git a/php/php.blade/src/org/netbeans/modules/php/blade/editor/parser/BladeDirectiveScope.java b/php/php.blade/src/org/netbeans/modules/php/blade/editor/parser/BladeDirectiveScope.java new file mode 100644 index 000000000000..25da0386397c --- /dev/null +++ b/php/php.blade/src/org/netbeans/modules/php/blade/editor/parser/BladeDirectiveScope.java @@ -0,0 +1,56 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.php.blade.editor.parser; + +import java.util.HashSet; +import java.util.Set; +/** + * + * @author bogdan + */ +public class BladeDirectiveScope { + + private final int bladeAntlrTokenType; + private final Set variables = new HashSet<>(); + private BladeDirectiveScope child; + + public BladeDirectiveScope(int tokenType) { + this.bladeAntlrTokenType = tokenType; + } + + public void addVariable(String varName) { + variables.add(varName); + } + + public Set getScopeVariables() { + return variables; + } + + public int getScopeType() { + return bladeAntlrTokenType; + } + + public void setChild(BladeDirectiveScope child) { + this.child = child; + } + + public BladeDirectiveScope getChild() { + return child; + } +} \ No newline at end of file diff --git a/php/php.blade/src/org/netbeans/modules/php/blade/editor/parser/BladeError.java b/php/php.blade/src/org/netbeans/modules/php/blade/editor/parser/BladeError.java new file mode 100644 index 000000000000..6fd2bfb6e33c --- /dev/null +++ b/php/php.blade/src/org/netbeans/modules/php/blade/editor/parser/BladeError.java @@ -0,0 +1,95 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.php.blade.editor.parser; + +import org.netbeans.modules.csl.api.Severity; +import org.openide.filesystems.FileObject; + +/** + * + * @author Petr Pisl + */ +@org.netbeans.api.annotations.common.SuppressWarnings({"EI_EXPOSE_REP", "EI_EXPOSE_REP2"}) +public class BladeError implements org.netbeans.modules.csl.api.Error.Badging { + private static final boolean SILENT_ERROR_BADGE = Boolean.getBoolean("nb.php.silent.error.badge"); //NOI18N + private final String displayName; + private final FileObject file; + private final int startPosition; + private final int endPosition; + private final Severity severity; + + public BladeError(String displayName, FileObject file, int startPosition, int endPosition, Severity severity) { + this.displayName = displayName; + this.file = file; + this.startPosition = startPosition; + this.endPosition = endPosition; + this.severity = severity; + } + + + @Override + public String getDisplayName() { + return this.displayName; + } + + @Override + public String getDescription() { + return null; + } + + @Override + public String getKey() { + return "[" + startPosition + "," + endPosition + "]-" + displayName; + } + + @Override + public FileObject getFile() { + return this.file; + } + + @Override + public int getStartPosition() { + return this.startPosition; + } + + @Override + public int getEndPosition() { + return this.endPosition; + } + + @Override + public Severity getSeverity() { + return this.severity; + } + + @Override + public Object[] getParameters() { + return new Object[]{}; + } + + @Override + public boolean isLineError() { + return true; + } + + @Override + public boolean showExplorerBadge() { + return !SILENT_ERROR_BADGE; + } +} diff --git a/php/php.blade/src/org/netbeans/modules/php/blade/editor/parser/BladeParser.java b/php/php.blade/src/org/netbeans/modules/php/blade/editor/parser/BladeParser.java new file mode 100644 index 000000000000..149d9a2041b3 --- /dev/null +++ b/php/php.blade/src/org/netbeans/modules/php/blade/editor/parser/BladeParser.java @@ -0,0 +1,72 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.php.blade.editor.parser; + +import java.util.logging.Level; +import java.util.logging.Logger; +import javax.swing.event.ChangeListener; +import org.netbeans.modules.parsing.api.Snapshot; +import org.netbeans.modules.parsing.api.Task; +import org.netbeans.modules.parsing.spi.ParseException; +import org.netbeans.modules.parsing.spi.SourceModificationEvent; + +/** + * + * @author bhaidu + */ +public class BladeParser extends org.netbeans.modules.parsing.spi.Parser { + + private static final Logger LOGGER = Logger.getLogger(BladeParser.class.getName()); + private BladeParserResult lastResult; + + @Override + public void parse(Snapshot snapshot, Task task, SourceModificationEvent event) throws ParseException { + if (snapshot == null) { + return; + } + + if (task.getClass().getName().contains("HtmlCssIndexContributor")) { // NOI18N + LOGGER.log(Level.INFO, "Skipped parsing for {0}", task.getClass().getName()); // NOI18N + return; + } + BladeParserResult parserResult = createParserResult(snapshot); + + BladeParserResult parsed = parserResult.get(task.getClass().getName()); + lastResult = parsed; + } + + @Override + public Result getResult(Task task) throws ParseException { + assert lastResult != null; + return lastResult; + } + + @Override + public void addChangeListener(ChangeListener changeListener) { + } + + @Override + public void removeChangeListener(ChangeListener changeListener) { + } + + protected BladeParserResult createParserResult(Snapshot snapshot) { + return new BladeParserResult(snapshot); + } + +} diff --git a/php/php.blade/src/org/netbeans/modules/php/blade/editor/parser/BladeParserResult.java b/php/php.blade/src/org/netbeans/modules/php/blade/editor/parser/BladeParserResult.java new file mode 100644 index 000000000000..b09acc4a6b89 --- /dev/null +++ b/php/php.blade/src/org/netbeans/modules/php/blade/editor/parser/BladeParserResult.java @@ -0,0 +1,241 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.php.blade.editor.parser; + +import java.util.ArrayList; +import java.util.List; +import org.antlr.v4.runtime.ANTLRErrorListener; +import org.antlr.v4.runtime.BaseErrorListener; +import org.netbeans.modules.csl.api.Error; +import org.netbeans.modules.csl.api.OffsetRange; +import org.netbeans.modules.csl.spi.ParserResult; +import org.netbeans.modules.parsing.api.Snapshot; +import org.antlr.v4.runtime.CharStream; +import org.antlr.v4.runtime.CharStreams; +import org.antlr.v4.runtime.CommonTokenStream; +import org.antlr.v4.runtime.ConsoleErrorListener; +import org.antlr.v4.runtime.RecognitionException; +import org.antlr.v4.runtime.Recognizer; +import org.antlr.v4.runtime.Token; +import org.netbeans.api.annotations.common.NonNull; +import org.netbeans.api.annotations.common.NullAllowed; +import org.netbeans.modules.csl.api.Severity; +import org.netbeans.modules.csl.spi.DefaultError; +import org.netbeans.modules.php.blade.editor.hints.BladeHintsProvider; +import org.netbeans.modules.php.blade.editor.navigator.BladeStructureItem; +import org.netbeans.modules.php.blade.editor.parser.listeners.CustomDirectivesListener; +import org.netbeans.modules.php.blade.editor.parser.listeners.PhpExpressionOccurenceListener; +import org.netbeans.modules.php.blade.editor.parser.listeners.ReferenceIdListener; +import org.netbeans.modules.php.blade.editor.parser.listeners.ScopeListener; +import org.netbeans.modules.php.blade.editor.parser.listeners.StructureListener; +import org.netbeans.modules.php.blade.syntax.antlr4.v10.BladeAntlrLexer; +import org.netbeans.modules.php.blade.syntax.antlr4.v10.BladeAntlrParser; +import org.openide.filesystems.FileObject; + +/** + * + * @author bhaidu + */ +public class BladeParserResult extends ParserResult { + + private final List errors = new ArrayList<>(); + private final List phpErrors = new ArrayList<>(); + public final List structure = new ArrayList<>(); + public final List folds = new ArrayList<>(); + public final BladeReferenceIdsCollection bladeRreferenceIdsCollection = new BladeReferenceIdsCollection(); + public final BladePhpExpressionOccurences bladePhpExpressionOccurences = new BladePhpExpressionOccurences(); + public final BladeCustomDirectiveOccurences bladeCustomDirectiveOccurences = new BladeCustomDirectiveOccurences(); + public final BladeScope bladeScope = new BladeScope(); + + private volatile boolean finished = false; + + public enum ReferenceType { + YIELD, STACK, SECTION, PUSH, PUSH_IF, PREPEND, INCLUDE, INCLUDE_IF, + INCLUDE_COND, EXTENDS, EACH, HAS_SECTION, + SECTION_MISSING, USE, INJECT, CUSTOM_DIRECTIVE, POSSIBLE_DIRECTIVE, + PHP_FUNCTION, PHP_CLASS, PHP_METHOD, PHP_CONSTANT, PHP_NAMESPACE, PHP_NAMESPACE_PATH_TYPE, + STATIC_FIELD_ACCESS, VITE_PATH, + TEMPLATE_PATH; + } + + public BladeParserResult(Snapshot snapshot) { + super(snapshot); + } + + protected BladeAntlrParser createParser(Snapshot snapshot) { + CharStream cs = CharStreams.fromString(String.valueOf(snapshot.getText())); + BladeAntlrLexer lexer = new BladeAntlrLexer(cs); + CommonTokenStream tokens = new CommonTokenStream(lexer); + BladeAntlrParser ret = new BladeAntlrParser(tokens); + ret.removeErrorListener(ConsoleErrorListener.INSTANCE); + return ret; + } + + public BladeParserResult get(String taskClass) { + if (!finished) { + String taskClassL = taskClass.toLowerCase(); + BladeAntlrParser parser = createParser(getSnapshot()); + parser.setBuildParseTree(false); + parser.addErrorListener(createErrorListener()); + parser.addParseListener(new ReferenceIdListener(bladeRreferenceIdsCollection)); + parser.addParseListener(new PhpExpressionOccurenceListener(bladePhpExpressionOccurences)); + + if (taskClassL.contains("completion")) { //NOI18N + parser.addParseListener(new ScopeListener(bladeScope)); + } + + //avoid on index + if (!taskClassL.contains(".indexing.repository")) { //NOI18N + parser.addParseListener(new CustomDirectivesListener(bladeCustomDirectiveOccurences)); + parser.addParseListener(new StructureListener(structure, folds, getFileObject())); + } + + evaluateParser(parser); + + finished = true; + } + + return this; + } + + protected void evaluateParser(BladeAntlrParser parser) { + parser.file(); + } + + @Override + protected boolean processingFinished() { + return finished; + } + + @Override + public List getDiagnostics() { + return errors; + } + + public void addError(Error error) { + errors.add(error); + } + + @Override + protected void invalidate() { + + } + + private ANTLRErrorListener createErrorListener() { + return new BaseErrorListener() { + @Override + public void syntaxError(Recognizer recognizer, Object offendingSymbol, int line, int charPositionInLine, String msg, RecognitionException e) { + int errorPosition = 0; + if (offendingSymbol instanceof Token) { + Token offendingToken = (Token) offendingSymbol; + errorPosition = offendingToken.getStartIndex(); + } + errors.add(new BladeError(null, msg, null, getFileObject(), errorPosition, errorPosition, Severity.ERROR)); + } + + }; + } + + public final FileObject getFileObject() { + return getSnapshot().getSource().getFileObject(); + } + + public BladeReferenceIdsCollection getBladeReferenceIdsCollection() { + return bladeRreferenceIdsCollection; + } + + public BladePhpExpressionOccurences getBladePhpExpressionOccurences() { + return bladePhpExpressionOccurences; + } + + public BladeCustomDirectiveOccurences getBladeCustomDirectiveOccurences() { + return bladeCustomDirectiveOccurences; + } + + public BladeScope getBladeScope() { + return bladeScope; + } + + public static class BladeStringReference { + + public final int antlrTokentype; + public final String identifier; + + public BladeStringReference(int antlrTokentype, String identifier) { + this.antlrTokentype = antlrTokentype; + this.identifier = identifier; + } + } + + public static class Reference { + + public final ReferenceType type; + public final String identifier; + public final String ownerClass; + public final String namespace; + public final OffsetRange defOffset; + + public Reference(ReferenceType type, String name, OffsetRange defOffset, String ownerClass) { + this.type = type; + this.identifier = name; + this.defOffset = defOffset; + this.ownerClass = ownerClass; + this.namespace = null; + } + + public Reference(ReferenceType type, String name, OffsetRange defOffset, String ownerClass, String namespace) { + this.type = type; + this.identifier = name; + this.defOffset = defOffset; + this.ownerClass = ownerClass; + this.namespace = namespace; + } + + public Reference(ReferenceType type, String name, OffsetRange defOffset) { + this.type = type; + this.identifier = name; + this.defOffset = defOffset; + this.ownerClass = null; + this.namespace = null; + } + } + + public enum FieldType { + PROPERTY, + CONSTANT, + METHOD; + } + + /** + * seems that java caches only this class ? BladeError is not found in some + * occasions + */ + public static class BladeError extends DefaultError implements org.netbeans.modules.csl.api.Error.Badging { + + public BladeError(@NullAllowed String key, @NonNull String displayName, @NullAllowed String description, @NonNull FileObject file, @NonNull int start, @NonNull int end, @NonNull Severity severity) { + super(key, displayName, description, file, start, end, severity); + } + + @Override + public boolean showExplorerBadge() { + return true; + } + + } +} diff --git a/php/php.blade/src/org/netbeans/modules/php/blade/editor/parser/BladePhpExpressionOccurences.java b/php/php.blade/src/org/netbeans/modules/php/blade/editor/parser/BladePhpExpressionOccurences.java new file mode 100644 index 000000000000..3d618d68e471 --- /dev/null +++ b/php/php.blade/src/org/netbeans/modules/php/blade/editor/parser/BladePhpExpressionOccurences.java @@ -0,0 +1,122 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.php.blade.editor.parser; + +import java.util.Set; +import java.util.TreeSet; +import org.netbeans.modules.csl.api.OffsetRange; + +/** + * + * @author bogdan + */ +public class BladePhpExpressionOccurences { + + private final Set phpRawInlineExpressionLocations = new TreeSet<>(); + private final Set phpInlineExpressionLocations = new TreeSet<>(); + private final Set phpOutputExpressionLocations = new TreeSet<>(); + private final Set phpForeachExpressionLocations = new TreeSet<>(); + + public void markPhpInlineExpressionOccurence(OffsetRange range) { + phpInlineExpressionLocations.add(range); + } + + public void markPhpOutputExpressionOccurence(OffsetRange range) { + phpOutputExpressionLocations.add(range); + } + + public void markPhpForeachExpressionOccurence(OffsetRange range) { + phpForeachExpressionLocations.add(range); + } + + public void markPhpRawInlineExpressionOccurence(OffsetRange range) { + phpRawInlineExpressionLocations.add(range); + } + + public OffsetRange findPhpExpressionLocation(int offset) { + + //OUTPUT + for (OffsetRange range : phpOutputExpressionLocations) { + + if (offset < range.getStart()) { + //excedeed the offset range + break; + } + + if (range.containsInclusive(offset)) { + return range; + } + } + + for (OffsetRange range : phpInlineExpressionLocations) { + + if (offset < range.getStart()) { + //excedeed the offset range + break; + } + + if (range.containsInclusive(offset)) { + return range; + } + } + + for (OffsetRange range : phpRawInlineExpressionLocations) { + + if (offset < range.getStart()) { + //excedeed the offset range + break; + } + + if (range.containsInclusive(offset)) { + return range; + } + } + + //FOREACH + for (OffsetRange range : phpForeachExpressionLocations) { + + if (offset < range.getStart()) { + //excedeed the offset range + break; + } + + if (range.containsInclusive(offset)) { + return range; + } + } + + return null; + } + + public Set getPhpInlineOccurences() { + return phpInlineExpressionLocations; + } + + public Set getPhpRawInlineOccurences() { + return phpRawInlineExpressionLocations; + } + + public Set getPhpOutputOccurences() { + return phpOutputExpressionLocations; + } + + public Set getPhpForeachOccurences() { + return phpForeachExpressionLocations; + } +} diff --git a/php/php.blade/src/org/netbeans/modules/php/blade/editor/parser/BladePhpSnippetParser.java b/php/php.blade/src/org/netbeans/modules/php/blade/editor/parser/BladePhpSnippetParser.java new file mode 100644 index 000000000000..2093ad8266ba --- /dev/null +++ b/php/php.blade/src/org/netbeans/modules/php/blade/editor/parser/BladePhpSnippetParser.java @@ -0,0 +1,327 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.php.blade.editor.parser; + +import java.util.Map; +import java.util.TreeMap; +import org.antlr.v4.runtime.ANTLRErrorListener; +import org.antlr.v4.runtime.BaseErrorListener; +import org.antlr.v4.runtime.CharStream; +import org.antlr.v4.runtime.CharStreams; +import org.antlr.v4.runtime.CommonTokenStream; +import org.antlr.v4.runtime.DefaultErrorStrategy; +import org.antlr.v4.runtime.FailedPredicateException; +import org.antlr.v4.runtime.Parser; +import org.antlr.v4.runtime.RecognitionException; +import org.antlr.v4.runtime.Recognizer; +import org.antlr.v4.runtime.Token; +import org.antlr.v4.runtime.tree.ParseTreeListener; +import org.netbeans.modules.csl.api.OffsetRange; +import org.netbeans.modules.csl.api.Severity; +import org.netbeans.modules.php.blade.editor.indexing.PhpIndexUtils.FieldAccessType; +import org.netbeans.modules.php.blade.editor.parser.BladeParserResult.BladeError; +import org.netbeans.modules.php.blade.syntax.antlr4.php.BladePhpAntlrLexer; +import org.netbeans.modules.php.blade.syntax.antlr4.php.BladePhpAntlrParser; +import org.netbeans.modules.php.blade.syntax.antlr4.php.BladePhpAntlrParserBaseListener; +import org.openide.filesystems.FileObject; + +/** + * + * @author bogdan + */ +public class BladePhpSnippetParser { + + private final String snippet; + private final FileObject originFile; + private final int snippetOffset; + private final Map identifierReference = new TreeMap<>(); + private final Map fieldAccessReference = new TreeMap<>(); + + public static final String PHP_START = ""; //NOI18N + + public enum PhpReferenceType { + PHP_NAMESPACE, + PHP_CLASS, + PHP_FUNCTION, + PHP_METHOD, + PHP_CLASS_CONSTANT + } + + public BladePhpSnippetParser(String snippet, FileObject originFile, int snippetOffset) { + this.snippet = snippet; + this.originFile = originFile; + this.snippetOffset = snippetOffset; + } + + public FileObject getOriginFile() { + return originFile; + } + + public int getSnippetOffset() { + return snippetOffset; + } + + public void parse() { + CharStream cs = CharStreams.fromString(snippet); + BladePhpAntlrLexer lexer = new BladePhpAntlrLexer(cs); + CommonTokenStream tokens = new CommonTokenStream(lexer); + BladePhpAntlrParser parser = new BladePhpAntlrParser(tokens); + parser.removeErrorListeners(); + parser.setErrorHandler(new BasicANTLRErrorStrategy()); + + parser.addParseListener(createIdentifiablePhpElementReferences()); + parser.expression(); + } + + private ParseTreeListener createIdentifiablePhpElementReferences() { + return new BladePhpAntlrParserBaseListener() { + @Override + public void exitFunctionExpr(BladePhpAntlrParser.FunctionExprContext ctx) { + if (ctx.IDENTIFIER() != null) { + Token token = ctx.IDENTIFIER().getSymbol(); + String functionName = ctx.IDENTIFIER().getText(); + OffsetRange range = new OffsetRange(token.getStartIndex(), token.getStopIndex() + 1); + PhpReference reference = new PhpReference(PhpReferenceType.PHP_FUNCTION, functionName); + identifierReference.put(range, reference); + } + } + + @Override + public void exitStaticFieldAccess(BladePhpAntlrParser.StaticFieldAccessContext ctx) { + String namespace = null; + Token classToken = ctx.className; + + if (classToken == null) { + return; + } + + if (ctx.namespace() != null) { + namespace = ctx.namespace().getText(); + namespace = namespace.substring(0, namespace.length() - 1); + OffsetRange namespaceRange = new OffsetRange(ctx.namespace().getStart().getStartIndex(), + ctx.namespace().getStop().getStopIndex()); + PhpReference reference = new PhpReference(PhpReferenceType.PHP_NAMESPACE, namespace, null); + identifierReference.put(namespaceRange, reference); + } + + OffsetRange range = new OffsetRange(classToken.getStartIndex(), classToken.getStopIndex() + 1); + PhpReference reference = new PhpReference(PhpReferenceType.PHP_CLASS, classToken.getText(), namespace); + identifierReference.put(range, reference); + + if (ctx.const_ != null) { + PhpReference methodReference = new PhpReference(PhpReferenceType.PHP_CLASS_CONSTANT, ctx.const_.getText(), namespace, reference); + OffsetRange accessRange = new OffsetRange(ctx.const_.getStartIndex(), ctx.const_.getStopIndex() + 1); + identifierReference.put(accessRange, methodReference); + FieldAcces fieldAccess = new FieldAcces(FieldAccessType.STATIC, methodReference, reference); + fieldAccessReference.put(accessRange, fieldAccess); + } + } + + @Override + public void exitStaticMethodAccess(BladePhpAntlrParser.StaticMethodAccessContext ctx) { + String namespace = null; + Token classToken = ctx.className; + + if (classToken == null) { + return; + } + if (ctx.namespace() != null) { + namespace = ctx.namespace().getText(); + //trim the extra \\ + namespace = namespace.substring(0, namespace.length() - 1); + OffsetRange namespaceRange = new OffsetRange(ctx.namespace().getStart().getStartIndex(), + ctx.namespace().getStop().getStopIndex()); + PhpReference reference = new PhpReference(PhpReferenceType.PHP_NAMESPACE, namespace, null); + identifierReference.put(namespaceRange, reference); + } + + OffsetRange range = new OffsetRange(classToken.getStartIndex(), classToken.getStopIndex() + 1); + + PhpReference reference = new PhpReference(PhpReferenceType.PHP_CLASS, classToken.getText(), namespace); + identifierReference.put(range, reference); + + if (ctx.method != null) { + PhpReference methodReference = new PhpReference(PhpReferenceType.PHP_METHOD, + ctx.method.getText(), namespace, reference); + OffsetRange accessRange = new OffsetRange(ctx.method.getStartIndex(), ctx.method.getStopIndex() + 1); + identifierReference.put(accessRange, methodReference); + FieldAcces fieldAccess = new FieldAcces(FieldAccessType.STATIC, methodReference, reference); + fieldAccessReference.put(accessRange, fieldAccess); + } + } + + @Override + public void exitStaticAccess(BladePhpAntlrParser.StaticAccessContext ctx) { + String namespace = null; + Token classToken = ctx.className; + if (classToken == null) { + return; + } + if (ctx.namespace() != null) { + namespace = ctx.namespace().getText(); + namespace = namespace.substring(0, namespace.length() - 1); + OffsetRange namespaceRange = new OffsetRange(ctx.namespace().getStart().getStartIndex(), + ctx.namespace().getStop().getStopIndex()); + PhpReference reference = new PhpReference(PhpReferenceType.PHP_NAMESPACE, namespace, null); + identifierReference.put(namespaceRange, reference); + } + + OffsetRange range = new OffsetRange(classToken.getStartIndex(), classToken.getStopIndex() + 1); + PhpReference reference = new PhpReference(PhpReferenceType.PHP_CLASS, classToken.getText(), namespace); + identifierReference.put(range, reference); + } + + @Override + public void exitClassInstanceStatement(BladePhpAntlrParser.ClassInstanceStatementContext ctx) { + String namespace = null; + if (ctx.namespace() != null) { + namespace = ctx.namespace().getText(); + //trim the extra \\ + namespace = namespace.substring(0, namespace.length() - 1); + OffsetRange namespaceRange = new OffsetRange(ctx.namespace().getStart().getStartIndex(), + ctx.namespace().getStop().getStopIndex()); + PhpReference reference = new PhpReference(PhpReferenceType.PHP_NAMESPACE, namespace, null); + identifierReference.put(namespaceRange, reference); + } + + Token classToken = ctx.className; + if (classToken != null && classToken.getStartIndex() > 0) { + OffsetRange range = new OffsetRange(classToken.getStartIndex(), classToken.getStopIndex() + 1); + PhpReference reference = new PhpReference(PhpReferenceType.PHP_CLASS, classToken.getText(), namespace); + identifierReference.put(range, reference); + } + } + + @Override + public void exitMisc(BladePhpAntlrParser.MiscContext ctx) { + String namespace = null; + if (ctx.namespace() != null) { + namespace = ctx.namespace().getText(); + //trim the extra \\ + namespace = namespace.substring(0, namespace.length() - 1); + OffsetRange namespaceRange = new OffsetRange(ctx.namespace().getStart().getStartIndex(), + ctx.namespace().getStop().getStopIndex()); + PhpReference reference = new PhpReference(PhpReferenceType.PHP_NAMESPACE, namespace, null); + identifierReference.put(namespaceRange, reference); + } + + Token classToken = ctx.className; + if (classToken != null && classToken.getStartIndex() > 0) { + OffsetRange range = new OffsetRange(classToken.getStartIndex(), classToken.getStopIndex() + 1); + PhpReference reference = new PhpReference(PhpReferenceType.PHP_CLASS, classToken.getText(), namespace); + identifierReference.put(range, reference); + } + } + }; + } + + public PhpReference findIdentifierReference(int offset) { + for (Map.Entry entry : identifierReference.entrySet()) { + OffsetRange range = entry.getKey(); + + if (offset < range.getStart()) { + //excedeed the offset range + break; + } + + if (range.containsInclusive(offset)) { + return entry.getValue(); + } + } + + return null; + } + + public FieldAcces findFieldAccessReference(int offset) { + for (Map.Entry entry : fieldAccessReference.entrySet()) { + OffsetRange range = entry.getKey(); + + if (offset < range.getStart()) { + //excedeed the offset range + break; + } + + if (range.containsInclusive(offset)) { + return entry.getValue(); + } + } + + return null; + } + + private final class BasicANTLRErrorStrategy extends DefaultErrorStrategy { + + @Override + protected void reportFailedPredicate(Parser recognizer, FailedPredicateException e) { + + } + + @Override + public void reportError(Parser recognizer, RecognitionException e) { + if (e.getMessage() == null) { + return; + } + super.reportError(recognizer, e); + } + } + + public static class PhpReference { + + public final PhpReferenceType type; + public final String identifier; + public final PhpReference ownerClass; + public final String namespace; + + public PhpReference(PhpReferenceType type, String name) { + this.type = type; + this.identifier = name; + this.namespace = null; + this.ownerClass = null; + } + + public PhpReference(PhpReferenceType type, String name, String namespace) { + this.type = type; + this.identifier = name; + this.namespace = namespace; + this.ownerClass = null; + } + + public PhpReference(PhpReferenceType type, String name, String namespace, PhpReference ownerClass) { + this.type = type; + this.identifier = name; + this.namespace = namespace; + this.ownerClass = ownerClass; + } + } + + public static class FieldAcces { + + public final FieldAccessType type; + public final PhpReference field; + public final PhpReference owner; + + public FieldAcces(FieldAccessType type, PhpReference field, PhpReference owner) { + this.type = type; + this.field = field; + this.owner = owner; + } + + } +} diff --git a/php/php.blade/src/org/netbeans/modules/php/blade/editor/parser/BladeReferenceIdsCollection.java b/php/php.blade/src/org/netbeans/modules/php/blade/editor/parser/BladeReferenceIdsCollection.java new file mode 100644 index 000000000000..118f10597cfd --- /dev/null +++ b/php/php.blade/src/org/netbeans/modules/php/blade/editor/parser/BladeReferenceIdsCollection.java @@ -0,0 +1,105 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.php.blade.editor.parser; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; +import org.antlr.v4.runtime.Token; +import org.netbeans.api.annotations.common.CheckForNull; +import org.netbeans.modules.csl.api.OffsetRange; +import org.netbeans.modules.php.blade.editor.EditorStringUtils; +import static org.netbeans.modules.php.blade.editor.parser.BladeParserResult.BladeStringReference; + +/** + * + * @author bogdan + */ +public class BladeReferenceIdsCollection { + + private final Map referenceIds = new TreeMap<>(); + private final Map> includePathsOccurences = new HashMap<>(); + + private final Map yieldIdsOccurences = new HashMap<>(); + private final Map stackIdsOccurences = new HashMap<>(); + + public String sanitizeIdentifier(Token identifiableStringToken) { + String rawReferenceId = identifiableStringToken.getText(); + return EditorStringUtils.stripSurroundingQuotes(rawReferenceId.trim()); + } + + public OffsetRange extractOffset(Token identifiableStringToken) { + //TODO extract the whitespace fragment + return new OffsetRange(identifiableStringToken.getStartIndex(), + identifiableStringToken.getStopIndex() + 1); + } + + public void addReferenceId(int type, String referenceId, OffsetRange range) { + BladeStringReference reference = new BladeStringReference(type, referenceId); + referenceIds.put(range, reference); + } + + @CheckForNull + public BladeStringReference findOccuredRefrence(int offset) { + for (Map.Entry entry : referenceIds.entrySet()) { + OffsetRange range = entry.getKey(); + + if (offset < range.getStart()) { + //excedeed the offset range + break; + } + + if (range.containsInclusive(offset)) { + return entry.getValue(); + } + } + + return null; + } + + public Map getReferenceIds() { + return referenceIds; + } + + public void markIncludeBladeOccurrence(String refName, OffsetRange or) { + includePathsOccurences.computeIfAbsent(refName, s -> new ArrayList<>()).add(or); + } + + public Map> getIncludePathsOccurences() { + return includePathsOccurences; + } + + public void addYieldOccurence(String identifier, OffsetRange range) { + yieldIdsOccurences.putIfAbsent(identifier, range); + } + + public Map getYieldIdOccurences() { + return yieldIdsOccurences; + } + + public void addStackOccurence(String identifier, OffsetRange range) { + stackIdsOccurences.putIfAbsent(identifier, range); + } + + public Map getStackIdOccurences() { + return stackIdsOccurences; + } +} diff --git a/php/php.blade/src/org/netbeans/modules/php/blade/editor/parser/BladeScope.java b/php/php.blade/src/org/netbeans/modules/php/blade/editor/parser/BladeScope.java new file mode 100644 index 000000000000..35a3dcd22502 --- /dev/null +++ b/php/php.blade/src/org/netbeans/modules/php/blade/editor/parser/BladeScope.java @@ -0,0 +1,59 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.php.blade.editor.parser; + +import java.util.Map; +import java.util.TreeMap; +import org.netbeans.modules.csl.api.OffsetRange; + +/** + * helps identifying block directive context. + * Ex: identifying if we have nested loops for foreach variable autocomplete + * @foreach($array1 as $item1) + * @foreach($array2 as $item2) + * + * @author bogdan + */ +public class BladeScope { + + private final Map scopeRange = new TreeMap<>(); + + + public void markScope(OffsetRange range, BladeDirectiveScope scope) { + scopeRange.put(range, scope); + } + + public BladeDirectiveScope findScope(int offset) { + for (Map.Entry entry : scopeRange.entrySet()) { + OffsetRange range = entry.getKey(); + + if (offset < range.getStart()) { + //excedeed the offset range + break; + } + + if (range.containsInclusive(offset)) { + return entry.getValue(); + } + } + + return null; + } + +} diff --git a/php/php.blade/src/org/netbeans/modules/php/blade/editor/parser/ParsingUtils.java b/php/php.blade/src/org/netbeans/modules/php/blade/editor/parser/ParsingUtils.java new file mode 100644 index 000000000000..df96faf0c30a --- /dev/null +++ b/php/php.blade/src/org/netbeans/modules/php/blade/editor/parser/ParsingUtils.java @@ -0,0 +1,153 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.php.blade.editor.parser; + +/** + * + * @author bogdan + */ +import java.io.IOException; +import java.util.Collections; +import javax.swing.text.BadLocationException; +import javax.swing.text.Document; +import org.netbeans.editor.BaseDocument; +import org.netbeans.modules.parsing.api.ParserManager; +import org.netbeans.modules.parsing.api.ResultIterator; +import org.netbeans.modules.parsing.api.Source; +import org.netbeans.modules.parsing.api.UserTask; +import org.netbeans.modules.parsing.spi.ParseException; +import org.netbeans.modules.parsing.spi.Parser; +import org.netbeans.modules.php.editor.parser.PHPParseResult; +import org.openide.filesystems.FileObject; +import org.openide.util.Exceptions; +import org.openide.cookies.EditorCookie; +import org.openide.loaders.DataObject; + +/** + * + * @author bogdan + */ +public final class ParsingUtils { + + public ParsingUtils() { + + } + + private PHPParseResult phpParserResult; + + public BaseDocument createPhpBaseDocument(String content) { + String mimeType = "text/x-php5"; //NOI18N + try { + BaseDocument doc = new BaseDocument(true, mimeType) { + @Override + public boolean isIdentifierPart(char ch) { + return super.isIdentifierPart(ch); + } + }; + + doc.putProperty("mimeType", mimeType); //NOI18N + doc.insertString(0,content, null); + + return doc; + } catch (BadLocationException ex) { + return null; + } + } + + public void parseFileObject(FileObject file) { + Document doc = openDocument(file); + + try { + Source source = Source.create(doc); + + if (source == null) { + return; + } + + Document sourceDoc = source.getDocument(false); + + if (sourceDoc == null) { + return; + } + + source.createSnapshot(); + ParserManager.parseWhenScanFinished(Collections.singletonList(source), new UserTask() { + + @Override + public void run(ResultIterator resultIterator) throws Exception { + Parser.Result parserResult = resultIterator.getParserResult(); + if (parserResult instanceof PHPParseResult) { + phpParserResult = (PHPParseResult) parserResult; + } + } + }); + + } catch (ParseException ex) { + Exceptions.printStackTrace(ex); + } + } + + public void laterParseFileObject(FileObject file) { + Document doc = openDocument(file); + + try { + Source source = Source.create(doc); + + if (source == null) { + return; + } + + Document sourceDoc = source.getDocument(false); + + if (sourceDoc == null) { + return; + } + + source.createSnapshot(); + ParserManager.parseWhenScanFinished(Collections.singletonList(source), new UserTask() { + + @Override + public void run(ResultIterator resultIterator) throws Exception { + Parser.Result parserResult = resultIterator.getParserResult(); + if (parserResult instanceof PHPParseResult) { + phpParserResult = (PHPParseResult) parserResult; + } + } + }); + + } catch (ParseException ex) { + Exceptions.printStackTrace(ex); + } + } + + public PHPParseResult getParserResult() { + return phpParserResult; + } + + private Document openDocument(FileObject f) { + try { + DataObject dataObject = DataObject.find(f); + EditorCookie ec = dataObject.getLookup().lookup(EditorCookie.class); + return ec.openDocument(); + } catch (IOException ex) { + throw new IllegalStateException(ex); + } + + } +} \ No newline at end of file diff --git a/php/php.blade/src/org/netbeans/modules/php/blade/editor/parser/listeners/CustomDirectivesListener.java b/php/php.blade/src/org/netbeans/modules/php/blade/editor/parser/listeners/CustomDirectivesListener.java new file mode 100644 index 000000000000..239aaa4d56d9 --- /dev/null +++ b/php/php.blade/src/org/netbeans/modules/php/blade/editor/parser/listeners/CustomDirectivesListener.java @@ -0,0 +1,48 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.php.blade.editor.parser.listeners; + +import org.antlr.v4.runtime.Token; +import org.netbeans.modules.csl.api.OffsetRange; +import org.netbeans.modules.php.blade.editor.parser.BladeCustomDirectiveOccurences; +import org.netbeans.modules.php.blade.syntax.antlr4.v10.BladeAntlrParser; +import org.netbeans.modules.php.blade.syntax.antlr4.v10.BladeAntlrParserBaseListener; + +/** + * + * @author bogdan + */ +public class CustomDirectivesListener extends BladeAntlrParserBaseListener { + + private final BladeCustomDirectiveOccurences phpExprOccurences; + + public CustomDirectivesListener(BladeCustomDirectiveOccurences phpExprOccurences) { + this.phpExprOccurences = phpExprOccurences; + } + + @Override + public void exitCustomDirective(BladeAntlrParser.CustomDirectiveContext ctx) { + if (ctx.D_CUSTOM() == null){ + return; + } + Token customDirective = ctx.D_CUSTOM().getSymbol(); + OffsetRange range = new OffsetRange(customDirective.getStartIndex() + 1, customDirective.getStopIndex()); + phpExprOccurences.markPhpExpressionOccurence(range, customDirective.getText()); + } +} diff --git a/php/php.blade/src/org/netbeans/modules/php/blade/editor/parser/listeners/PhpExpressionOccurenceListener.java b/php/php.blade/src/org/netbeans/modules/php/blade/editor/parser/listeners/PhpExpressionOccurenceListener.java new file mode 100644 index 000000000000..3226dec4c05d --- /dev/null +++ b/php/php.blade/src/org/netbeans/modules/php/blade/editor/parser/listeners/PhpExpressionOccurenceListener.java @@ -0,0 +1,194 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.php.blade.editor.parser.listeners; + +import org.antlr.v4.runtime.Token; +import org.netbeans.modules.csl.api.OffsetRange; +import org.netbeans.modules.php.blade.editor.parser.BladePhpExpressionOccurences; +import org.netbeans.modules.php.blade.syntax.antlr4.v10.BladeAntlrParser; +import org.netbeans.modules.php.blade.syntax.antlr4.v10.BladeAntlrParserBaseListener; + +/** + * + * @author bogdan + */ +public class PhpExpressionOccurenceListener extends BladeAntlrParserBaseListener { + + private final BladePhpExpressionOccurences phpExprOccurences; + + public PhpExpressionOccurenceListener(BladePhpExpressionOccurences phpExprOccurences) { + this.phpExprOccurences = phpExprOccurences; + } + + @Override + public void exitIdentifiableArgDirective(BladeAntlrParser.IdentifiableArgDirectiveContext ctx) { + if (ctx.LPAREN() == null || ctx.RPAREN() == null) { + return; + } + + Token leftParen = ctx.LPAREN().getSymbol(); + Token rightParen = ctx.RPAREN().getSymbol(); + + if (leftParen.getStartIndex() + 1 >= rightParen.getStopIndex()) { + return; + } + + markPhpOutputExprOccurence(leftParen, rightParen, 1); + } + + @Override + public void exitForeachLoopArguments(BladeAntlrParser.ForeachLoopArgumentsContext ctx) { + if (ctx.LPAREN() == null || ctx.RPAREN() == null) { + return; + } + + Token leftParen = ctx.LPAREN().getSymbol(); + Token rightParen = ctx.RPAREN().getSymbol(); + + if (leftParen.getStartIndex() + 1 >= rightParen.getStopIndex()) { + return; + } + + markPhpForeachExprOccurence(leftParen, rightParen, 1); + } + + @Override + public void exitMultipleArgDirective(BladeAntlrParser.MultipleArgDirectiveContext ctx) { + if (ctx.LPAREN() == null || ctx.RPAREN() == null) { + return; + } + + Token leftParen = ctx.LPAREN().getSymbol(); + Token rightParen = ctx.RPAREN().getSymbol(); + + if (leftParen.getStartIndex() + 1 >= rightParen.getStopIndex()) { + return; + } + + markPhpOutputExprOccurence(leftParen, rightParen, 1); + } + + @Override + public void exitDirectiveArguments(BladeAntlrParser.DirectiveArgumentsContext ctx) { + if (ctx.LPAREN() == null || ctx.RPAREN() == null) { + return; + } + Token leftParen = ctx.LPAREN().getSymbol(); + Token rightParen = ctx.RPAREN().getSymbol(); + markPhpOutputExprOccurence(leftParen, rightParen, 1); + } + + @Override + public void exitBlockIdentifiableArgDirective(BladeAntlrParser.BlockIdentifiableArgDirectiveContext ctx) { + if (ctx.LPAREN() == null || ctx.RPAREN() == null) { + return; + } + Token leftParen = ctx.LPAREN().getSymbol(); + Token rightParen = ctx.RPAREN().getSymbol(); + markPhpOutputExprOccurence(leftParen, rightParen, 1); + } + + @Override + public void exitBladeContentTags(BladeAntlrParser.BladeContentTagsContext ctx) { + if (ctx.BLADE_CONTENT_OPEN_TAG() == null || ctx.BLADE_CONTENT_CLOSE_TAG() == null) { + return; + } + Token openTag = ctx.BLADE_CONTENT_OPEN_TAG().getSymbol(); + Token closeTag = ctx.BLADE_CONTENT_CLOSE_TAG().getSymbol(); + markPhpOutputExprOccurence(openTag, closeTag, 1); + } + + @Override + public void exitBladeRawTags(BladeAntlrParser.BladeRawTagsContext ctx) { + if (ctx.start == null || ctx.stop == null) { + return; + } + Token openTag = ctx.start; + Token closeTag = ctx.stop; + markPhpOutputExprOccurence(openTag, closeTag, 1); + } + + @Override + public void exitBladePhpBlock(BladeAntlrParser.BladePhpBlockContext ctx) { + if (ctx.D_PHP() == null || ctx.D_ENDPHP() == null) { + return; + } + Token openTag = ctx.D_PHP().getSymbol(); + Token closeTag = ctx.D_ENDPHP().getSymbol(); + markPhpExprOccurence(openTag, closeTag, 1); + } + + @Override + public void exitPhpInline(BladeAntlrParser.PhpInlineContext ctx) { + if (ctx.start == null || ctx.stop == null) { + return; + } + Token openTag = ctx.start; + Token closeTag = ctx.stop; + markPhpRawExprOccurence(openTag, closeTag, 1); + } + + private void markPhpExprOccurence(Token start, Token end, int offset) { + int startOffset = start.getStopIndex() + offset; + int endOffset = end.getStartIndex(); + + if (startOffset > endOffset) { + return; + } + + OffsetRange range = new OffsetRange(startOffset, endOffset); + phpExprOccurences.markPhpInlineExpressionOccurence(range); + } + + private void markPhpRawExprOccurence(Token start, Token end, int offset) { + int startOffset = start.getStopIndex() + offset; + int endOffset = end.getStartIndex(); + + if (startOffset > endOffset) { + return; + } + + OffsetRange range = new OffsetRange(startOffset, endOffset); + phpExprOccurences.markPhpRawInlineExpressionOccurence(range); + } + + private void markPhpOutputExprOccurence(Token start, Token end, int offset) { + int startOffset = start.getStopIndex() + offset; + int endOffset = end.getStartIndex(); + + if (startOffset > endOffset) { + return; + } + + OffsetRange range = new OffsetRange(startOffset, endOffset); + phpExprOccurences.markPhpOutputExpressionOccurence(range); + } + + private void markPhpForeachExprOccurence(Token start, Token end, int offset) { + int startOffset = start.getStopIndex() + offset; + int endOffset = end.getStartIndex(); + + if (startOffset > endOffset) { + return; + } + + OffsetRange range = new OffsetRange(startOffset, endOffset); + phpExprOccurences.markPhpForeachExpressionOccurence(range); + } +} diff --git a/php/php.blade/src/org/netbeans/modules/php/blade/editor/parser/listeners/ReferenceIdListener.java b/php/php.blade/src/org/netbeans/modules/php/blade/editor/parser/listeners/ReferenceIdListener.java new file mode 100644 index 000000000000..033640106fbc --- /dev/null +++ b/php/php.blade/src/org/netbeans/modules/php/blade/editor/parser/listeners/ReferenceIdListener.java @@ -0,0 +1,113 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.php.blade.editor.parser.listeners; + +import org.antlr.v4.runtime.Token; +import org.antlr.v4.runtime.tree.TerminalNode; +import org.netbeans.modules.csl.api.OffsetRange; +import org.netbeans.modules.php.blade.editor.parser.BladeReferenceIdsCollection; +import static org.netbeans.modules.php.blade.syntax.antlr4.v10.BladeAntlrLexer.*; +import org.netbeans.modules.php.blade.syntax.antlr4.v10.BladeAntlrParser; +import org.netbeans.modules.php.blade.syntax.antlr4.v10.BladeAntlrParserBaseListener; + +/** + * + * @author bogdan + */ +public class ReferenceIdListener extends BladeAntlrParserBaseListener { + + private final BladeReferenceIdsCollection referenceIdsCollection; + + public ReferenceIdListener(BladeReferenceIdsCollection referenceIdsCollection) { + this.referenceIdsCollection = referenceIdsCollection; + } + + @Override + public void exitIdentifiableArgDirective(BladeAntlrParser.IdentifiableArgDirectiveContext ctx) { + int tokenType = ctx.getStart().getType(); + + if (ctx.IDENTIFIABLE_STRING() == null || ctx.IDENTIFIABLE_STRING().getSymbol() == null) { + return; + } + + Token token = ctx.IDENTIFIABLE_STRING().getSymbol(); + String identifier = referenceIdsCollection.sanitizeIdentifier(token); + OffsetRange range = referenceIdsCollection.extractOffset(token); + + referenceIdsCollection.addReferenceId(tokenType, identifier, range); + + switch (tokenType) { + case D_EXTENDS: + case D_INCLUDE: + case D_INCLUDE_IF: + case D_INCLUDE_WHEN: + case D_INCLUDE_UNLESS: { + referenceIdsCollection.markIncludeBladeOccurrence(identifier, range); + break; + } + case D_YIELD: { + referenceIdsCollection.addYieldOccurence(identifier, range); + break; + } + case D_STACK: { + referenceIdsCollection.addStackOccurence(identifier, range); + break; + } + } + } + + @Override + public void exitBlockIdentifiableArgDirective(BladeAntlrParser.BlockIdentifiableArgDirectiveContext ctx) { + if (ctx.IDENTIFIABLE_STRING() == null || ctx.IDENTIFIABLE_STRING().getSymbol() == null) { + return; + } + + Token identifierToken = ctx.IDENTIFIABLE_STRING().getSymbol(); + addIdentifierReference(ctx.getStart(), identifierToken); + } + + @Override + public void exitMultipleArgDirective(BladeAntlrParser.MultipleArgDirectiveContext ctx) { + int tokenType = ctx.getStart().getType(); + + for (TerminalNode identifierNode : ctx.IDENTIFIABLE_STRING()) { + Token identifierToken = identifierNode.getSymbol(); + if (identifierToken != null) { + String identifier = referenceIdsCollection.sanitizeIdentifier(identifierToken); + OffsetRange range = referenceIdsCollection.extractOffset(identifierToken); + + referenceIdsCollection.addReferenceId(tokenType, identifier, range); + + switch (tokenType) { + case D_EACH: { + referenceIdsCollection.markIncludeBladeOccurrence(identifier, range); + break; + } + } + } + } + } + + private void addIdentifierReference(Token directive, Token token) { + String identifier = referenceIdsCollection.sanitizeIdentifier(token); + OffsetRange range = referenceIdsCollection.extractOffset(token); + + referenceIdsCollection.addReferenceId(directive.getType(), identifier, range); + } +} diff --git a/php/php.blade/src/org/netbeans/modules/php/blade/editor/parser/listeners/ScopeListener.java b/php/php.blade/src/org/netbeans/modules/php/blade/editor/parser/listeners/ScopeListener.java new file mode 100644 index 000000000000..b11d2fb1ef82 --- /dev/null +++ b/php/php.blade/src/org/netbeans/modules/php/blade/editor/parser/listeners/ScopeListener.java @@ -0,0 +1,94 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.php.blade.editor.parser.listeners; + +import org.antlr.v4.runtime.Token; +import org.netbeans.modules.csl.api.OffsetRange; +import org.netbeans.modules.php.blade.editor.parser.BladeDirectiveScope; +import org.netbeans.modules.php.blade.editor.parser.BladeScope; +import org.netbeans.modules.php.blade.syntax.antlr4.v10.BladeAntlrParser; +import org.netbeans.modules.php.blade.syntax.antlr4.v10.BladeAntlrParserBaseListener; +/** + * + * @author bogdan + */ +public class ScopeListener extends BladeAntlrParserBaseListener { + + private final BladeScope bladeScope; + private BladeDirectiveScope bufferScope; + int bladeScopeBalance = 0; + + public ScopeListener(BladeScope bladeScope) { + this.bladeScope = bladeScope; + } + + @Override + public void enterForeachStatement(BladeAntlrParser.ForeachStatementContext ctx) { + if (bufferScope == null) { + bufferScope = new BladeDirectiveScope(ctx.start.getType()); + } else { + bufferScope.setChild(new BladeDirectiveScope(ctx.start.getType())); + } + bladeScopeBalance++; + } + + @Override + public void exitForeachLoopArguments(BladeAntlrParser.ForeachLoopArgumentsContext ctx) { + if (bufferScope == null) { + return; + } + if (ctx.LPAREN() == null || ctx.RPAREN() == null) { + return; + } + + Token leftParen = ctx.LPAREN().getSymbol(); + Token rightParen = ctx.RPAREN().getSymbol(); + + if (leftParen.getStartIndex() + 1 >= rightParen.getStopIndex()) { + return; + } + + if (ctx.main_array != null) { + bufferScope.addVariable(ctx.main_array.getText()); + } + + if (ctx.array_item != null) { + bufferScope.addVariable(ctx.array_item.getText()); + } + + if (ctx.array_value != null) { + bufferScope.addVariable(ctx.array_value.getText()); + } + } + + @Override + public void exitForeachStatement(BladeAntlrParser.ForeachStatementContext ctx) { + Token start = ctx.start; + Token stop = ctx.stop; + + OffsetRange range = new OffsetRange(start.getStartIndex(), + stop.getStopIndex() + 1); + + bladeScopeBalance--; + if (bufferScope != null && bladeScopeBalance == 0) { + bladeScope.markScope(range, bufferScope); + bufferScope = null; + } + } +} diff --git a/php/php.blade/src/org/netbeans/modules/php/blade/editor/parser/listeners/StructureListener.java b/php/php.blade/src/org/netbeans/modules/php/blade/editor/parser/listeners/StructureListener.java new file mode 100644 index 000000000000..27c3c36df0c4 --- /dev/null +++ b/php/php.blade/src/org/netbeans/modules/php/blade/editor/parser/listeners/StructureListener.java @@ -0,0 +1,204 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.php.blade.editor.parser.listeners; + +import java.util.ArrayList; +import java.util.List; +import org.antlr.v4.runtime.Token; +import org.antlr.v4.runtime.tree.TerminalNode; +import org.netbeans.modules.csl.api.OffsetRange; +import org.netbeans.modules.php.blade.editor.EditorStringUtils; +import org.netbeans.modules.php.blade.editor.navigator.BladeStructureItem; +import org.netbeans.modules.php.blade.editor.navigator.DirectiveStructureItem.DirectiveBlockStructureItem; +import org.netbeans.modules.php.blade.editor.navigator.DirectiveStructureItem.DirectiveInlineStructureItem; +import org.netbeans.modules.php.blade.syntax.antlr4.v10.BladeAntlrParser; +import org.netbeans.modules.php.blade.syntax.antlr4.v10.BladeAntlrParserBaseListener; +import org.openide.filesystems.FileObject; + +/** + * + * @author bogdan + */ +public class StructureListener extends BladeAntlrParserBaseListener { + + public final List structure; + public final List folds; + private final FileObject file; + + private final List inlineNestedElements = new ArrayList<>(); + private final List blockNestedElements = new ArrayList<>(); + private DirectiveBlockStructureItem previousItem; + private int depth = 0; + private String identifier; + + public StructureListener(final List structure, final List folds, FileObject file) { + this.structure = structure; + this.folds = folds; + this.file = file; + } + + @Override + public void enterBlockDirective(BladeAntlrParser.BlockDirectiveContext ctx) { + identifier = null; + depth++; + } + + @Override + public void enterBlockIdentifiableArgDirective(BladeAntlrParser.BlockIdentifiableArgDirectiveContext ctx) { + identifier = null; + depth++; + } + + @Override + public void exitBlockDirective(BladeAntlrParser.BlockDirectiveContext ctx) { + depth--; + + Token directiveToken = ctx.getStart(); + Token endToken = ctx.getStop(); + addBlockDirective(directiveToken, endToken); + } + + @Override + public void exitBlockIdentifiableArgDirective(BladeAntlrParser.BlockIdentifiableArgDirectiveContext ctx) { + depth--; + identifier = null; + Token directiveToken = ctx.getStart(); + Token endToken = ctx.getStop(); + markIdentifier(ctx.IDENTIFIABLE_STRING()); + addBlockDirective(directiveToken, endToken); + } + + @Override + public void exitIdentifiableArgDirective(BladeAntlrParser.IdentifiableArgDirectiveContext ctx) { + Token directiveToken = ctx.getStart(); + + if (directiveToken == null) { + return; + } + identifier = null; + markIdentifier(ctx.IDENTIFIABLE_STRING()); + addInlineDirective(directiveToken); + } + + @Override + public void exitMultipleArgDirective(BladeAntlrParser.MultipleArgDirectiveContext ctx) { + Token directiveToken = ctx.getStart(); + + if (directiveToken == null) { + return; + } + + identifier = null; + addInlineDirective(directiveToken); + } + + @Override + public void exitInlineDirective(BladeAntlrParser.InlineDirectiveContext ctx) { + Token directiveToken = ctx.getStart(); + + if (directiveToken == null) { + return; + } + + identifier = null; + addInlineDirective(directiveToken); + } + + @Override + public void exitCustomDirective(BladeAntlrParser.CustomDirectiveContext ctx) { + Token directiveToken = ctx.getStart(); + + if (directiveToken == null) { + return; + } + + identifier = null; + addInlineDirective(directiveToken); + } + + private void addInlineDirective(Token directiveToken) { + DirectiveInlineStructureItem inlineElement; + String directiveName = directiveToken.getText().trim(); + + inlineElement = new DirectiveInlineStructureItem(directiveName, identifier, + file, directiveToken.getStartIndex(), directiveToken.getStopIndex() + 1); + + if (depth > 0) { + inlineNestedElements.add(inlineElement); + } else { + structure.add(inlineElement); + } + } + + private void markIdentifier(TerminalNode identifierNode) { + identifier = null; + if (identifierNode != null) { + Token identifiableStringToken = identifierNode.getSymbol(); + + if (identifiableStringToken != null && identifiableStringToken.getText().length() >= 3) { + String bladeParamText = identifiableStringToken.getText(); + identifier = EditorStringUtils.stripSurroundingQuotes(bladeParamText); + } + } + } + + private void addBlockDirective(Token directiveToken, Token endToken) { + + if (directiveToken == null) { + return; + } + + String directiveName = directiveToken.getText().trim(); + DirectiveBlockStructureItem blockItem = new DirectiveBlockStructureItem(directiveName, identifier, + file, directiveToken.getStartIndex(), endToken.getStopIndex() + 1); + + blockItem.setDepth(depth); + + if (!inlineNestedElements.isEmpty()) { + blockItem.nestedItems.addAll(inlineNestedElements); + inlineNestedElements.clear(); + } + if (previousItem != null && depth > 0 && previousItem.getDepth() == depth) { + blockNestedElements.add(blockItem); + } else if (previousItem != null && depth > 0 && previousItem.getDepth() > depth) { + blockItem.nestedItems.addAll(blockNestedElements); + blockNestedElements.clear(); + blockNestedElements.add(blockItem); + } else if (depth > 0) { + blockNestedElements.add(blockItem); + } else { + blockItem.nestedItems.addAll(blockNestedElements); + blockNestedElements.clear(); + structure.add(blockItem); + } + + previousItem = blockItem; + //folds + int start = directiveToken.getStartIndex() + 1 + directiveName.length(); + int end = endToken.getStartIndex();//the start of the close directive + + if (start > end) { + return; + } + OffsetRange range = new OffsetRange(start, end); + if (!folds.contains(range)) { + folds.add(range); + } + } +} \ No newline at end of file diff --git a/php/php.blade/src/org/netbeans/modules/php/blade/editor/path/BladePathUtils.java b/php/php.blade/src/org/netbeans/modules/php/blade/editor/path/BladePathUtils.java new file mode 100644 index 000000000000..4b5cd114985d --- /dev/null +++ b/php/php.blade/src/org/netbeans/modules/php/blade/editor/path/BladePathUtils.java @@ -0,0 +1,346 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.php.blade.editor.path; + +import java.io.File; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import org.netbeans.api.project.Project; +import org.netbeans.modules.php.blade.editor.BladeLanguage; +import org.netbeans.modules.php.blade.project.BladeProjectProperties; +import org.netbeans.modules.php.blade.project.ProjectUtils; +import org.netbeans.modules.php.blade.syntax.StringUtils; +import org.netbeans.spi.project.ui.support.ProjectConvertors; +import org.openide.filesystems.FileObject; +import org.openide.filesystems.FileUtil; + +/** + * + * @author bogdan + */ +public final class BladePathUtils { + + public static final String LARAVEL_RESOURCES = "resources"; //NOI18N + public static final String LARAVEL_VIEW_FOLDER = "views"; //NOI18N + public static final String LARAVEL_VIEW_PATH = LARAVEL_RESOURCES + StringUtils.FORWARD_SLASH + LARAVEL_VIEW_FOLDER; + + private BladePathUtils() { + + } + + /** + * first we need to extract the root folder of view after we apply a generic + * path sanitize for blade paths (ex "my.path" -> "my/path.blade.php") + * + * @param contextFile + * @param viewPath + * @return List + */ + public static List findFileObjectsForBladeViewPath(FileObject contextFile, String viewPath) { + List fileViewAssociationList = new ArrayList<>(); + Project project = ProjectConvertors.getNonConvertorOwner(contextFile); + + if (project == null) { + return fileViewAssociationList; + } + + HashSet viewRoots = getDefaultRoots(project); + viewRoots.addAll(getCustomViewsRoots(project, contextFile)); + + if (viewRoots.isEmpty()) { + return fileViewAssociationList; + } + + String sanitizedBladePath = viewPathToFilePath(viewPath); + + for (FileObject viewRoot : viewRoots) { + FileObject includedFile = viewRoot.getFileObject(sanitizedBladePath, true); + + if (includedFile != null && includedFile.isValid()) { + fileViewAssociationList.add(includedFile); + } + } + + return fileViewAssociationList; + } + + public static FileObject findFileObjectForBladeViewPath(FileObject contextFile, String viewPath) { + FileObject res = null; + Project project = ProjectConvertors.getNonConvertorOwner(contextFile); + + if (project == null) { + return res; + } + + HashSet viewRoots = getDefaultRoots(project); + viewRoots.addAll(getCustomViewsRoots(project, contextFile)); + + if (viewRoots.isEmpty()) { + return res; + } + + String sanitizedBladePath = viewPathToFilePath(viewPath); + + for (FileObject viewRoot : viewRoots) { + FileObject includedFile = viewRoot.getFileObject(sanitizedBladePath, true); + + if (includedFile != null && includedFile.isValid()) { + return includedFile; + } + } + + return res; + } + + /** + * + * + * @param contextFile + * @param prefixViewPath + * @return List + */ + public static List getParentChildrenFromPrefixPath(FileObject contextFile, + String prefixViewPath) { + List list = new ArrayList<>(); + //this should be a fallback + Project project = ProjectConvertors.getNonConvertorOwner(contextFile); + + if (project == null) { + return list; + } + + List viewRoots = getDefaultRootsList(project); + viewRoots.addAll(getCustomViewsRoots(project, contextFile)); + + if (viewRoots.isEmpty()) { + return list; + } + + String unixPath = prefixViewPath.replace(StringUtils.DOT, StringUtils.FORWARD_SLASH); + + int lastSlash = unixPath.lastIndexOf(StringUtils.FORWARD_SLASH); + + return filterFilesFromRootFolder(viewRoots.toArray(new FileObject[0]), unixPath, lastSlash); + } + + public static List filterFilesFromRootFolder(FileObject[] viewRoots, String unixPath, int lastSlash) { + + List list = new ArrayList<>(); + + if (lastSlash > 0) { + //filter only relative folders + return filterFromRelativeSlash(viewRoots, unixPath, lastSlash); + } else { + for (FileObject rootFolder : viewRoots) { + for (FileObject file : rootFolder.getChildren()) { + String filePath = file.getPath().replace(rootFolder.getPath() + StringUtils.FORWARD_SLASH, ""); //NOI18N + if (filePath.startsWith(unixPath)) { + list.add(file); + } + } + } + + } + + return list; + } + + public static List filterFromRelativeSlash(FileObject[] viewRoots, String unixPath, int lastSlash) { + Map relativeFilePathMap = new HashMap<>(); + List list = new ArrayList<>(); + boolean folderEnd = unixPath.endsWith(StringUtils.FORWARD_SLASH); + for (FileObject rootFolder : viewRoots) { + FileObject relativeViewRoot = rootFolder.getFileObject(unixPath.substring(0, lastSlash)); + + if (relativeViewRoot != null && relativeViewRoot.isValid()) { + relativeFilePathMap.put(unixPath, relativeViewRoot); + } + } + + if (folderEnd) { + for (Map.Entry entry : relativeFilePathMap.entrySet()) { + list.addAll(Arrays.asList(entry.getValue().getChildren())); + } + } else { + String relativePrefixToCompare = unixPath.substring(lastSlash + 1, unixPath.length()); + for (Map.Entry entry : relativeFilePathMap.entrySet()) { + for (FileObject file : entry.getValue().getChildren()) { + if (file.getName().startsWith(relativePrefixToCompare)) { + list.add(file); + } + } + } + } + + return list; + } + + public static List getCustomViewsRoots(Project project, FileObject contextFile) { + List list = new ArrayList<>(); + + BladeProjectProperties bladeProperties = BladeProjectProperties.getInstance(project); + if (bladeProperties == null) { + return list; + } + String[] views = bladeProperties.getViewsFolderPathList(); + + if (views.length > 0) { + views = Arrays.stream(views).filter(s -> !s.isEmpty()).toArray(String[]::new); + Arrays.sort(views, (String s1, String s2) -> { + //clear empty configs + if (s1 == null || s2 == null) { + return 0; + } + return s2.length() - s1.length();// comparision + }); + for (String view : views) { + if (view.length() == 0) { + continue; + } + File viewPath = new File(view); + if (!viewPath.exists()) { + continue; + } + + list.add(FileUtil.toFileObject(viewPath)); + } + } + return list; + } + + public static String toBladeViewPath(FileObject file) { + String path = null; + Project project = ProjectUtils.getMainOwner(file); + + if (project == null) { + return path; + } + + String filePath = file.getPath(); + FileObject defaultLaravelPath = project.getProjectDirectory().getFileObject(LARAVEL_VIEW_PATH); + + if (defaultLaravelPath != null) { + //belongs to the default folder + String viewFolderPath = defaultLaravelPath.getPath(); + if (filePath.startsWith(viewFolderPath)) { + String bladePath = BladePathUtils.toBladeViewPath(filePath.replace(viewFolderPath, "")); //NOI18N + //starting slash + if (bladePath.startsWith(StringUtils.DOT)) { + bladePath = bladePath.substring(1, bladePath.length()); + } + return bladePath; + } + } + + BladeProjectProperties bladeProperties = BladeProjectProperties.getInstance(project); + + if (bladeProperties == null) { + return path; + } + + String[] viewFolders = bladeProperties.getViewsFolderPathList(); + + for (String viewFolder : viewFolders) { + if (viewFolder.length() == 0) { + continue; + } + File viewPath = new File(viewFolder); + if (!viewPath.exists()) { + continue; + } + + //we need to keep the same format + FileObject viewFile = FileUtil.toFileObject(viewPath); + String viewFileAbsPath = viewFile.getPath(); + if (filePath.startsWith(viewFileAbsPath)) { + String relativePath = filePath.replace(viewFileAbsPath, ""); //NOI18N + if (!relativePath.startsWith(StringUtils.FORWARD_SLASH)) { + //it doesn't belong to the folder + continue; + } + return BladePathUtils.toBladeViewPath(relativePath.substring(1)); + } + } + + return path; + } + + public static String getRelativeProjectPath(FileObject currentFile) { + Project projectOwner = ProjectConvertors.getNonConvertorOwner(currentFile); + if (projectOwner == null) { + return ""; //NOI18N + } + + String dirPath = projectOwner.getProjectDirectory().getPath(); + String relativePath = currentFile.getPath().replace(dirPath, ""); //NOI18N + + //only if we found the relative project path + if (currentFile.getPath().length() > relativePath.length()) { + return relativePath; + } + + return ""; //NOI18N + } + + public static String toBladeViewPath(String filePath) { + return filePath.replace(BladeLanguage.FILE_EXTENSION_WITH_DOT, "").replace(StringUtils.FORWARD_SLASH, StringUtils.DOT); //NOI18N + } + + public static String viewPathToFilePath(String viewPath) { + return viewPath.replace(StringUtils.DOT, StringUtils.FORWARD_SLASH) + BladeLanguage.FILE_EXTENSION_WITH_DOT; + } + + public static HashSet getDefaultRoots(Project project) { + HashSet defaultList = new HashSet<>(); + FileObject defaultViewsRoot = project.getProjectDirectory().getFileObject(LARAVEL_VIEW_PATH); + + if (defaultViewsRoot != null && defaultViewsRoot.isValid()) { + defaultList.add(defaultViewsRoot); + } + + return defaultList; + } + + public static List getDefaultRootsList(Project project) { + ArrayList list = new ArrayList<>(); + FileObject defaultViewsRoot = project.getProjectDirectory().getFileObject(LARAVEL_VIEW_PATH); + + if (defaultViewsRoot != null && defaultViewsRoot.isValid()) { + list.add(defaultViewsRoot); + } + + return list; + } + + public static String toBladeToProjectFilePath(String path) { + return LARAVEL_VIEW_PATH + StringUtils.FORWARD_SLASH + viewPathToFilePath(path); + } + + public static String removeViewsFolderFromPath(String filePath) { + int viewsPos = filePath.indexOf(StringUtils.FORWARD_SLASH + BladePathUtils.LARAVEL_VIEW_FOLDER + StringUtils.FORWARD_SLASH); + if (viewsPos < 0) { + return filePath; + } + return filePath.substring(viewsPos, filePath.length()); + } +} diff --git a/php/php.blade/src/org/netbeans/modules/php/blade/editor/preferences/GeneralPreferencesUtils.java b/php/php.blade/src/org/netbeans/modules/php/blade/editor/preferences/GeneralPreferencesUtils.java new file mode 100644 index 000000000000..c34e71bf47ba --- /dev/null +++ b/php/php.blade/src/org/netbeans/modules/php/blade/editor/preferences/GeneralPreferencesUtils.java @@ -0,0 +1,84 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.php.blade.editor.preferences; + +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.prefs.PreferenceChangeEvent; +import java.util.prefs.PreferenceChangeListener; +import java.util.prefs.Preferences; +import org.netbeans.api.editor.mimelookup.MimeLookup; +import org.netbeans.modules.php.blade.editor.BladeLanguage; +import org.openide.util.WeakListeners; + +/** + * this will be moved to PHP Options + * + * @author bogdan + */ +public final class GeneralPreferencesUtils { + + private static final AtomicBoolean INITED = new AtomicBoolean(false); + + public static final String ENABLE_FORMATTING = "enable-blade-format"; // NOI18N + public static final String ENABLE_INDENTATION = "enable-blade-indent"; // NOI18N + public static final String ENABLE_AUTO_TAG_COMPLETION = "enable-auto-tag-completion"; // NOI18N + + private static Boolean enableFormatting = null; + private static Boolean enableIndentation = null; + private static Boolean enableAutoTagCompletion = null; + + // default values + private static Preferences PREFERENCES; + + private GeneralPreferencesUtils(){ + + } + + private static final PreferenceChangeListener PREFERENCES_TRACKER = new PreferenceChangeListener() { + @Override + public void preferenceChange(PreferenceChangeEvent evt) { + enableFormatting = PREFERENCES.getBoolean(ENABLE_FORMATTING, false); + enableIndentation = PREFERENCES.getBoolean(ENABLE_INDENTATION, false); + enableAutoTagCompletion = PREFERENCES.getBoolean(ENABLE_AUTO_TAG_COMPLETION, false); + } + }; + + public static boolean isFormattingEnabled(){ + lazyInit(); + return enableFormatting; + } + + public static boolean isIndentationEnabled(){ + lazyInit(); + return enableIndentation; + } + + public static boolean isAutoTagCompletionEnabled(){ + lazyInit(); + return enableAutoTagCompletion; + } + + private static void lazyInit() { + if (INITED.compareAndSet(false, true)) { + PREFERENCES = MimeLookup.getLookup(BladeLanguage.MIME_TYPE).lookup(Preferences.class); + PREFERENCES.addPreferenceChangeListener(WeakListeners.create(PreferenceChangeListener.class, PREFERENCES_TRACKER, PREFERENCES)); + PREFERENCES_TRACKER.preferenceChange(null); + } + } +} diff --git a/php/php.blade/src/org/netbeans/modules/php/blade/editor/preferences/ModulePreferences.java b/php/php.blade/src/org/netbeans/modules/php/blade/editor/preferences/ModulePreferences.java new file mode 100644 index 000000000000..98790bec04fb --- /dev/null +++ b/php/php.blade/src/org/netbeans/modules/php/blade/editor/preferences/ModulePreferences.java @@ -0,0 +1,42 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.php.blade.editor.preferences; + +import java.util.prefs.Preferences; +import org.netbeans.api.editor.mimelookup.MimeLookup; +import org.netbeans.modules.php.blade.editor.BladeLanguage; +import static org.netbeans.modules.php.blade.editor.preferences.GeneralPreferencesUtils.ENABLE_AUTO_TAG_COMPLETION; + +/** + * + * @author bogdan + */ +public class ModulePreferences { + public static Preferences getPreferences(){ + return MimeLookup.getLookup(BladeLanguage.MIME_TYPE).lookup(Preferences.class); + } + + public static void setPrefBoolean(String key, boolean value){ + getPreferences().putBoolean(key, value); + } + + public static boolean isAutoTagCompletionEnabled(){ + return ModulePreferences.getPreferences().getBoolean(ENABLE_AUTO_TAG_COMPLETION, true); + } +} diff --git a/php/php.blade/src/org/netbeans/modules/php/blade/editor/refactoring/BladePathInfo.java b/php/php.blade/src/org/netbeans/modules/php/blade/editor/refactoring/BladePathInfo.java new file mode 100644 index 000000000000..528a48d06c5f --- /dev/null +++ b/php/php.blade/src/org/netbeans/modules/php/blade/editor/refactoring/BladePathInfo.java @@ -0,0 +1,73 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.php.blade.editor.refactoring; + + +import java.util.Objects; +import org.openide.filesystems.FileObject; + +/** + * + * @author bogdan + */ +public class BladePathInfo { + + private final FileObject sourceFile; + private final String bladePath; + + public BladePathInfo(FileObject sourceFile, String bladePath) { + this.sourceFile = sourceFile; + this.bladePath = bladePath; + } + + public FileObject getSourceFile() { + return sourceFile; + } + + public String getBladePath() { + return bladePath; + } + + @Override + public int hashCode() { + int hash = 7; + hash = 53 * hash + Objects.hashCode(this.sourceFile); + hash = 53 * hash + Objects.hashCode(this.bladePath); + return hash; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final BladePathInfo other = (BladePathInfo) obj; + if (!Objects.equals(this.bladePath, other.getBladePath())) { + return false; + } + return Objects.equals(this.sourceFile, other.getSourceFile()); + } + +} diff --git a/php/php.blade/src/org/netbeans/modules/php/blade/editor/refactoring/BladePathUsage.java b/php/php.blade/src/org/netbeans/modules/php/blade/editor/refactoring/BladePathUsage.java new file mode 100644 index 000000000000..0703164f5162 --- /dev/null +++ b/php/php.blade/src/org/netbeans/modules/php/blade/editor/refactoring/BladePathUsage.java @@ -0,0 +1,203 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.php.blade.editor.refactoring; + +import java.io.IOException; +import java.util.List; +import java.util.concurrent.CancellationException; +import java.util.concurrent.atomic.AtomicBoolean; +import javax.swing.text.BadLocationException; +import javax.swing.text.Position; +import javax.swing.text.StyledDocument; +import org.netbeans.api.editor.document.LineDocument; +import org.netbeans.api.editor.document.LineDocumentUtils; +import org.netbeans.modules.php.blade.editor.BladeLanguage; +import org.netbeans.modules.php.blade.editor.indexing.BladeIndex.IndexedOffsetReference; +import org.netbeans.modules.php.blade.editor.indexing.QueryUtils; + +import org.netbeans.modules.refactoring.api.AbstractRefactoring; +import org.netbeans.modules.refactoring.api.Problem; +import org.netbeans.modules.refactoring.api.WhereUsedQuery; +import org.netbeans.modules.refactoring.spi.RefactoringElementsBag; +import org.netbeans.modules.refactoring.spi.RefactoringPlugin; +import org.netbeans.modules.refactoring.spi.RefactoringPluginFactory; +import org.netbeans.modules.refactoring.spi.SimpleRefactoringElementImplementation; +import org.openide.cookies.EditorCookie; +import org.openide.filesystems.FileObject; +import org.openide.text.CloneableEditorSupport; +import org.openide.text.PositionBounds; +import org.openide.util.Exceptions; +import org.openide.util.Lookup; +import org.openide.util.lookup.ServiceProvider; + +/** + * + * @author bogdan + */ +public class BladePathUsage { + + private static final class WhereUsedRefactoringPlugin implements RefactoringPlugin { + + private final WhereUsedQuery query; + private final BladePathInfo bladeFileReference; + private final AtomicBoolean cancel = new AtomicBoolean(); + + public WhereUsedRefactoringPlugin(WhereUsedQuery query, BladePathInfo bladeFileReference) { + this.query = query; + this.bladeFileReference = bladeFileReference; + } + + @Override + public Problem preCheck() { + return null; + } + + @Override + public Problem checkParameters() { + return null; + } + + @Override + public Problem fastCheckParameters() { + return null; + } + + @Override + public void cancelRequest() { + cancel.set(true); + } + + @Override + public Problem prepare(RefactoringElementsBag refactoringElements) { + try { + FileObject sourceFO = this.bladeFileReference.getSourceFile(); + + if (!BladeLanguage.MIME_TYPE.equals(sourceFO.getMIMEType())) { + return null; + } + + List references = QueryUtils.getIncludePathReferences(this.bladeFileReference.getBladePath(), sourceFO); + + for (IndexedOffsetReference reference : references) { + if (cancel.get()) { + throw new CancellationException(); + } + + PositionBounds bounds; + FileObject fo = reference.getOriginFile(); + int start = reference.getStart(); + int end = start + reference.getReference().length(); + try { + CloneableEditorSupport es = fo.getLookup().lookup(CloneableEditorSupport.class); + EditorCookie ec = fo.getLookup().lookup(EditorCookie.class); + StyledDocument doc = ec.openDocument(); + LineDocument ldoc = (LineDocument) doc; + + int rowStart = LineDocumentUtils.getLineStart(ldoc, start); + int rowEnd = LineDocumentUtils.getLineEnd(ldoc, end); + + bounds = new PositionBounds( + es.createPositionRef(start, Position.Bias.Forward), + es.createPositionRef(end, Position.Bias.Forward) + ); + + String lineText = doc.getText(rowStart, rowEnd - rowStart); + //offset quote symbols + String annotatedLine + = lineText.substring(0, start - rowStart + 1) + + "" //NOI18N + + lineText.substring(start - rowStart + 1, end - rowStart + 2) + + "" //NOI18N + + lineText.substring(end - rowStart + 2); + refactoringElements.add(query, new BladeRefactoringElementImpl(annotatedLine, fo, bounds)); + } catch (BadLocationException | IOException ex) { + Exceptions.printStackTrace(ex); + } + } + + return null; + } catch (CancellationException ex) { + return new Problem(false, "Cancelled"); + } + } + + } + + @ServiceProvider(service = RefactoringPluginFactory.class) + public static class FactoryImpl implements RefactoringPluginFactory { + + @Override + public RefactoringPlugin createInstance(AbstractRefactoring refactoring) { + if (refactoring instanceof WhereUsedQuery) { + WhereUsedQuery q = (WhereUsedQuery) refactoring; + BladePathInfo symbolInformation = q.getRefactoringSource().lookup(BladePathInfo.class); + if (symbolInformation != null) { + return new WhereUsedRefactoringPlugin(q, symbolInformation); + } + } + return null; + } + + } + + public static class BladeRefactoringElementImpl extends SimpleRefactoringElementImplementation { + + private final String annotatedLine; + private final FileObject file; + private final PositionBounds bounds; + + public BladeRefactoringElementImpl(String annotatedLine, FileObject file, PositionBounds bounds) { + this.annotatedLine = annotatedLine; + this.file = file; + this.bounds = bounds; + } + + @Override + public String getText() { + return "Element usage"; //NOI18N + } + + @Override + public String getDisplayText() { + return annotatedLine; + } + + @Override + public void performChange() { + // Currently the BladeRefactoringElementImpl is only used for the + // WhereUsedRefactoring, which is not doing changes + throw new UnsupportedOperationException(); + } + + @Override + public Lookup getLookup() { + return Lookup.EMPTY; + } + + @Override + public FileObject getParentFile() { + return file; + } + + @Override + public PositionBounds getPosition() { + return bounds; + } + } +} diff --git a/php/php.blade/src/org/netbeans/modules/php/blade/editor/refactoring/RefactoringActionsProvider.java b/php/php.blade/src/org/netbeans/modules/php/blade/editor/refactoring/RefactoringActionsProvider.java new file mode 100644 index 000000000000..7cd17e15e90a --- /dev/null +++ b/php/php.blade/src/org/netbeans/modules/php/blade/editor/refactoring/RefactoringActionsProvider.java @@ -0,0 +1,175 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.php.blade.editor.refactoring; + +import java.io.IOException; +import javax.swing.JEditorPane; +import javax.swing.SwingUtilities; +import javax.swing.text.AbstractDocument; +import javax.swing.text.Document; +import org.netbeans.api.lexer.TokenHierarchy; +import org.netbeans.api.lexer.TokenSequence; +import org.netbeans.modules.editor.NbEditorUtilities; +import org.netbeans.modules.php.blade.editor.BladeLanguage; +import org.netbeans.modules.refactoring.api.ui.ExplorerContext; +import org.netbeans.modules.refactoring.spi.ui.ActionsImplementationProvider; +import org.netbeans.modules.refactoring.spi.ui.UI; +import org.openide.cookies.*; +import org.openide.filesystems.FileObject; +import org.openide.filesystems.FileUtil; +import org.openide.loaders.DataObject; +import org.openide.nodes.Node; +import org.openide.text.CloneableEditorSupport; +import org.openide.text.NbDocument; +import org.openide.util.Exceptions; +import org.openide.util.Lookup; +import org.openide.util.NbBundle; +import org.openide.util.NbBundle.Messages; +import org.openide.windows.TopComponent; + +/** + * + * @author bogdan + */ +@NbBundle.Messages({"WARN_CannotPerformHere=Cannot perform rename here."}) +@org.openide.util.lookup.ServiceProvider(service = org.netbeans.modules.refactoring.spi.ui.ActionsImplementationProvider.class, position = 300) +public class RefactoringActionsProvider extends ActionsImplementationProvider { + + @Override + @Messages("NM_Unknown=Unknown") + public void doFindUsages(Lookup lookup) { + Runnable start = () -> { + EditorCookie ec = lookup.lookup(EditorCookie.class); + + if (isFromEditor(ec)) { + JEditorPane c = ec.getOpenedPanes()[0]; + Document doc = c.getDocument(); + AbstractDocument abstractDoc = (doc instanceof AbstractDocument) ? ((AbstractDocument) doc) : null; + FileObject file = NbEditorUtilities.getFileObject(doc); + int caretPos = c.getCaretPosition(); + + String name = "Unknown"; //NOI18N + + if (abstractDoc != null) { + abstractDoc.readLock(); + } + try { + TokenSequence ts = TokenHierarchy.get(doc).tokenSequence(); + if (ts != null) { + ts.move(caretPos); + if (ts.moveNext()) { + name = ts.token().text().toString(); + } + } + } finally { + if (abstractDoc != null) { + abstractDoc.readUnlock(); + } + } + + BladePathInfo si = new BladePathInfo(file, name); + UI.openRefactoringUI(new WhereBladePathUsedRefactoringUIImpl(si), + TopComponent.getRegistry().getActivated()); + } + }; + SwingUtilities.invokeLater(start); + } + + @Override + public boolean canCopy(Lookup lookup) { + boolean isBlade = isBlade(lookup); + if (!isBlade) { + return super.canCopy(lookup); + } + return true; + } + + @Override + public void doCopy(final Lookup lookup) { + + final FileObject dir = getTarget(lookup); + + //should treat multiple files + Runnable start = () -> { + Node node = lookup.lookup(Node.class); + FileObject file = node.getLookup().lookup(FileObject.class); + FileObject dirTarget = (dir!= null && dir.isFolder()) ? dir : file.getParent(); + String bladeExtension = BladeLanguage.FILE_EXTENSION_WITH_DOT; + String name = file.getNameExt(); + String existingFileName = file.getNameExt(); + int counter = 1; + while(counter < 50 && dirTarget.getFileObject(existingFileName)!= null){ + //shouldn't be the case + existingFileName = name.substring(0, name.length() - bladeExtension.length()) + "_" + counter + bladeExtension; //NOI18N + counter++; + } + + String incrementSuffix = counter > 1 ? "_" + (counter - 1) : ""; //NOI18N + String resultName = existingFileName.substring(0, name.length() - bladeExtension.length()) + incrementSuffix; + try { + FileUtil.copyFile(file, dirTarget, resultName, BladeLanguage.FILE_EXTENSION); + } catch (IOException ex) { + Exceptions.printStackTrace(ex); + } + }; + SwingUtilities.invokeLater(start); + } + + public boolean isBlade(Lookup lookup) { + boolean ret = false; + Node node = lookup.lookup(Node.class); + + if (node != null && node.getDisplayName().endsWith(BladeLanguage.FILE_EXTENSION_WITH_DOT)) { + ret = true; + } + return ret; + } + + public static FileObject getTarget(Lookup look) { + ExplorerContext drop = look.lookup(ExplorerContext.class); + if (drop == null) { + return null; + } + Node n = drop.getTargetNode(); + if (n == null) { + return null; + } + DataObject dob = n.getLookup().lookup(DataObject.class); + if (dob != null) { + return dob.getPrimaryFile(); + } + return null; + } + + @Override + public boolean canFindUsages(Lookup lookup) { + return isBlade(lookup); + } + + public static boolean isFromEditor(EditorCookie ec) { + if (ec != null && NbDocument.findRecentEditorPane(ec) != null) { + TopComponent activetc = TopComponent.getRegistry().getActivated(); + if (activetc instanceof CloneableEditorSupport.Pane) { + return true; + } + } + return false; + } + +} diff --git a/php/php.blade/src/org/netbeans/modules/php/blade/editor/refactoring/WhereBladePathUsedRefactoringUIImpl.java b/php/php.blade/src/org/netbeans/modules/php/blade/editor/refactoring/WhereBladePathUsedRefactoringUIImpl.java new file mode 100644 index 000000000000..bdd16a76b86f --- /dev/null +++ b/php/php.blade/src/org/netbeans/modules/php/blade/editor/refactoring/WhereBladePathUsedRefactoringUIImpl.java @@ -0,0 +1,91 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.php.blade.editor.refactoring; + +import javax.swing.event.ChangeListener; +import org.netbeans.modules.refactoring.api.AbstractRefactoring; +import org.netbeans.modules.refactoring.api.Problem; +import org.netbeans.modules.refactoring.api.WhereUsedQuery; +import org.netbeans.modules.refactoring.spi.ui.CustomRefactoringPanel; +import org.netbeans.modules.refactoring.spi.ui.RefactoringUI; +import org.openide.util.HelpCtx; +import org.openide.util.NbBundle.Messages; +import org.openide.util.lookup.Lookups; + +/** + * + * @author bogdan + */ +public class WhereBladePathUsedRefactoringUIImpl implements RefactoringUI { + private final BladePathInfo symbolInformation; + + public WhereBladePathUsedRefactoringUIImpl(BladePathInfo symbolInformation) { + this.symbolInformation = symbolInformation; + } + + @Override + public String getName() { + return symbolInformation.getBladePath(); + } + + @Override + @Messages({ + "# {0} - identifier", + "DESC_Usages=Usages of {0}" + }) + public String getDescription() { + return Bundle.DESC_Usages(symbolInformation.getBladePath()); + } + + @Override + public boolean isQuery() { + return true; + } + + @Override + public CustomRefactoringPanel getPanel(ChangeListener parent) { + return null; + } + + @Override + public Problem setParameters() { + return null; + } + + @Override + public Problem checkParameters() { + return null; + } + + @Override + public boolean hasParameters() { + return false; + } + + @Override + public AbstractRefactoring getRefactoring() { + return new WhereUsedQuery(Lookups.fixed(symbolInformation)); + } + + @Override + public HelpCtx getHelpCtx() { + return HelpCtx.DEFAULT_HELP; + } + +} diff --git a/php/php.blade/src/org/netbeans/modules/php/blade/editor/typinghooks/BladeTypedTextInterceptor.java b/php/php.blade/src/org/netbeans/modules/php/blade/editor/typinghooks/BladeTypedTextInterceptor.java new file mode 100644 index 000000000000..fd817a010bc0 --- /dev/null +++ b/php/php.blade/src/org/netbeans/modules/php/blade/editor/typinghooks/BladeTypedTextInterceptor.java @@ -0,0 +1,187 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.php.blade.editor.typinghooks; + +import java.util.Map; +import java.util.WeakHashMap; +import javax.swing.text.BadLocationException; +import javax.swing.text.Document; +import org.netbeans.modules.php.blade.editor.BladeLanguage; +import org.netbeans.api.editor.mimelookup.MimePath; +import org.netbeans.api.editor.mimelookup.MimeRegistration; +import org.netbeans.api.editor.mimelookup.MimeRegistrations; +import org.netbeans.api.lexer.Token; +import org.netbeans.api.lexer.TokenHierarchy; +import org.netbeans.api.lexer.TokenSequence; +import org.netbeans.modules.php.blade.editor.lexer.BladeTokenId; +import static org.netbeans.modules.php.blade.editor.lexer.BladeTokenId.HTML; +import org.netbeans.modules.php.blade.editor.preferences.ModulePreferences; +import org.netbeans.spi.editor.typinghooks.TypedTextInterceptor; + +/** + * auto complete for '[', '(', '\'', '"' and blade tags + * + * @author bhaidu + */ +public class BladeTypedTextInterceptor implements TypedTextInterceptor { + + static final Map CHAR_PAIR = new WeakHashMap<>(); + + public static enum TagType { + CONTENT, + RAW, + COMMENT + } + /** + * auto complete char pair + */ + static { + CHAR_PAIR.put('(', ')'); + CHAR_PAIR.put('[', ']'); + CHAR_PAIR.put('\'', '\''); + CHAR_PAIR.put('"', '"'); + } + + @Override + public boolean beforeInsert(Context cntxt) throws BadLocationException { + return false; + } + + @Override + public void insert(MutableContext context) throws BadLocationException { + if (context.getReplacedText().length() != 0) { + return; + } + + char ch = context.getText().charAt(0); + + if (CHAR_PAIR.containsKey(ch)) { + completePairChar(context, ch, CHAR_PAIR.get(ch)); + return; + } + + if (!isAutoTagCompletionEnabled()){ + return; + } + + String typedText = context.getText(); + + Map TagParts = new WeakHashMap<>(); + TagParts.put("{", TagType.CONTENT); //NOI18N + TagParts.put("!", TagType.RAW); //NOI18N + TagParts.put("-", TagType.COMMENT); //NOI18N + + TagType tagType = TagParts.get(typedText); + + if (tagType == null){ + return; + } + + int offset = context.getOffset(); + + switch(tagType) { + case CONTENT -> { + if (offset < 1) { + return; + } + } + case RAW, COMMENT -> { + if (offset < 2) { + return; + } + } + } + + Document document = context.getDocument(); + TokenHierarchy th = TokenHierarchy.get(document); + TokenSequence ts = th.tokenSequence(); + ts.move(offset - 1); + ts.moveNext(); + + Token token = ts.token(); + + if (token == null || !(token.id() instanceof BladeTokenId)){ + return; + } + + BladeTokenId bladeTokenId = (BladeTokenId) token.id(); + + String tokenText = token.text().toString().trim(); + + switch (bladeTokenId) { + case HTML -> { + int startOffset = Math.min(tokenText.length(), 3); + String snippet = document.getText(offset - startOffset, startOffset); + if (snippet.endsWith("{") && tagType.equals(TagType.CONTENT)){ //NOI18N + context.setText("{ }}", 1); //NOI18N + } else if (snippet.endsWith("{!") && tagType.equals(TagType.RAW )){ //NOI18N + context.setText("! !!}", 1); //NOI18N + } else if (snippet.endsWith("{{-") && tagType.equals(TagType.COMMENT)){ //NOI18N + context.setText("- --}}", 1); //NOI18N + } + } + } + } + + /** + * simple char context completion + * + * @param context + * @param chopen + * @param chclose + */ + private void completePairChar(MutableContext context, char chopen, char chclose) { + StringBuilder sb = new StringBuilder(); + sb.append(chopen); + sb.append(chclose); + String text = sb.toString(); + context.setText(text, 1); + } + + @Override + public void afterInsert(Context cntxt) throws BadLocationException { + + } + + @Override + public void cancelled(Context cntxt) { + + } + + public boolean isAutoTagCompletionEnabled(){ + return ModulePreferences.isAutoTagCompletionEnabled(); + } + + /** + * register for HTML also + */ + @MimeRegistrations(value = { + @MimeRegistration(mimeType = BladeLanguage.MIME_TYPE, service = TypedTextInterceptor.Factory.class), + @MimeRegistration(mimeType = "text/html", service = TypedTextInterceptor.Factory.class) + }) + public static class Factory implements TypedTextInterceptor.Factory { + + @Override + public TypedTextInterceptor createTypedTextInterceptor(MimePath mimePath) { + return new BladeTypedTextInterceptor(); + } + + } + +} diff --git a/php/php.blade/src/org/netbeans/modules/php/blade/editor/ui/customizer/BladeComponents.form b/php/php.blade/src/org/netbeans/modules/php/blade/editor/ui/customizer/BladeComponents.form new file mode 100644 index 000000000000..c4d4ba04a788 --- /dev/null +++ b/php/php.blade/src/org/netbeans/modules/php/blade/editor/ui/customizer/BladeComponents.form @@ -0,0 +1,144 @@ + + +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/php/php.blade/src/org/netbeans/modules/php/blade/editor/ui/customizer/BladeComponents.java b/php/php.blade/src/org/netbeans/modules/php/blade/editor/ui/customizer/BladeComponents.java new file mode 100644 index 000000000000..4c78fb080315 --- /dev/null +++ b/php/php.blade/src/org/netbeans/modules/php/blade/editor/ui/customizer/BladeComponents.java @@ -0,0 +1,176 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.php.blade.editor.ui.customizer; + +import java.awt.EventQueue; +import java.io.File; +import org.netbeans.api.project.Project; +import org.netbeans.modules.php.blade.project.BladeProjectProperties; +import org.netbeans.modules.php.blade.project.ComponentsSupport; +import org.openide.filesystems.FileChooserBuilder; +import org.openide.filesystems.FileObject; +import org.openide.filesystems.FileUtil; + +/** + * + * @author bhaidu + */ +public class BladeComponents extends javax.swing.JPanel { + + private final Project project; + private final BladeProjectProperties bladeProperties; + private File bladeComponentFolder; + + /** + * Creates new form CustomizerIncludePath + * + * @param project + */ + public BladeComponents(Project project) { + this.project = project; + bladeProperties = BladeProjectProperties.getInstance(project); + initComponents(); + init(); + } + + private void init() { + //the model reference is enough + customBladeComponentClassFolderList.setModel(bladeProperties.getModelForBladeComponentsClassFolderList()); + } + + public void storeData() { + bladeProperties.storeBladeComponentsFolder(); + ComponentsSupport componentSupport = ComponentsSupport.getInstance(project); + + if (bladeComponentFolder != null) { + FileObject root = FileUtil.toFileObject(bladeComponentFolder); + componentSupport.scanBladeComponentsClassFolder(root); + } + } + + /** + * This method is called from within the constructor to initialize the form. + * WARNING: Do NOT modify this code. The content of this method is always + * regenerated by the Form Editor. + */ + @SuppressWarnings("unchecked") + // //GEN-BEGIN:initComponents + private void initComponents() { + + includePathLabel = new javax.swing.JLabel(); + compilerPathFileButton = new javax.swing.JButton(); + jScrollPane1 = new javax.swing.JScrollPane(); + customBladeComponentClassFolderList = new javax.swing.JList<>(); + removePathButton = new javax.swing.JButton(); + jLabel1 = new javax.swing.JLabel(); + + includePathLabel.setFont(new java.awt.Font("Segoe UI", 1, 12)); // NOI18N + org.openide.awt.Mnemonics.setLocalizedText(includePathLabel, org.openide.util.NbBundle.getMessage(BladeComponents.class, "BladeComponents.includePathLabel.text_1")); // NOI18N + + org.openide.awt.Mnemonics.setLocalizedText(compilerPathFileButton, org.openide.util.NbBundle.getMessage(BladeComponents.class, "BladeComponents.compilerPathFileButton.text_1")); // NOI18N + compilerPathFileButton.setToolTipText(org.openide.util.NbBundle.getMessage(BladeComponents.class, "BladeComponents.compilerPathFileButton.toolTipText_1")); // NOI18N + compilerPathFileButton.setActionCommand(org.openide.util.NbBundle.getMessage(BladeComponents.class, "BladeComponents.compilerPathFileButton.actionCommand_1")); // NOI18N + compilerPathFileButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + compilerPathFileButtonActionPerformed(evt); + } + }); + + jScrollPane1.setViewportView(customBladeComponentClassFolderList); + + org.openide.awt.Mnemonics.setLocalizedText(removePathButton, org.openide.util.NbBundle.getMessage(BladeComponents.class, "BladeComponents.removePathButton.text_1")); // NOI18N + removePathButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + removePathButtonActionPerformed(evt); + } + }); + + jLabel1.setForeground(new java.awt.Color(102, 102, 102)); + org.openide.awt.Mnemonics.setLocalizedText(jLabel1, org.openide.util.NbBundle.getMessage(BladeComponents.class, "BladeComponents.jLabel1.text")); // NOI18N + + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); + this.setLayout(layout); + layout.setHorizontalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addContainerGap() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() + .addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 474, Short.MAX_VALUE) + .addGap(18, 18, 18) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false) + .addComponent(compilerPathFileButton, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(removePathButton, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))) + .addGroup(layout.createSequentialGroup() + .addComponent(includePathLabel) + .addGap(0, 0, Short.MAX_VALUE))) + .addContainerGap()) + .addGroup(layout.createSequentialGroup() + .addGap(23, 23, 23) + .addComponent(jLabel1) + .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + ); + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addGap(20, 20, 20) + .addComponent(includePathLabel) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jLabel1) + .addGap(33, 33, 33) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(jScrollPane1, javax.swing.GroupLayout.PREFERRED_SIZE, 143, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGroup(layout.createSequentialGroup() + .addComponent(compilerPathFileButton) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(removePathButton))) + .addGap(0, 68, Short.MAX_VALUE)) + ); + }// //GEN-END:initComponents + + private void compilerPathFileButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_compilerPathFileButtonActionPerformed + assert EventQueue.isDispatchThread(); + bladeComponentFolder = new FileChooserBuilder(BladeComponents.class) + .setDirectoriesOnly(true) + .setTitle("Select BladeComponents class folder") // NOI18N + .setDefaultWorkingDirectory(FileUtil.toFile(project.getProjectDirectory())) + .forceUseOfDefaultWorkingDirectory(true) + .showOpenDialog(); + if (bladeComponentFolder != null) { + bladeProperties.addCustomBladeComponentClassFolder(FileUtil.normalizeFile(bladeComponentFolder).getAbsolutePath()); + } + }//GEN-LAST:event_compilerPathFileButtonActionPerformed + + private void removePathButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_removePathButtonActionPerformed + int index = customBladeComponentClassFolderList.getSelectedIndex(); + if (index > -1) { + bladeProperties.removeCustomBladeComponentClassFolder(index); + } + + }//GEN-LAST:event_removePathButtonActionPerformed + + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JButton compilerPathFileButton; + private javax.swing.JList customBladeComponentClassFolderList; + private javax.swing.JLabel includePathLabel; + private javax.swing.JLabel jLabel1; + private javax.swing.JScrollPane jScrollPane1; + private javax.swing.JButton removePathButton; + // End of variables declaration//GEN-END:variables +} diff --git a/php/php.blade/src/org/netbeans/modules/php/blade/editor/ui/customizer/BladeComponentsCustomizerProvider.java b/php/php.blade/src/org/netbeans/modules/php/blade/editor/ui/customizer/BladeComponentsCustomizerProvider.java new file mode 100644 index 000000000000..c5cea3c6a1fe --- /dev/null +++ b/php/php.blade/src/org/netbeans/modules/php/blade/editor/ui/customizer/BladeComponentsCustomizerProvider.java @@ -0,0 +1,65 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.php.blade.editor.ui.customizer; + +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import javax.swing.JComponent; +import org.netbeans.api.project.Project; +import org.netbeans.spi.project.ui.support.ProjectCustomizer; +import org.openide.util.Lookup; +import org.openide.util.NbBundle; + +/** + * + * @author bhaidu + */ +public class BladeComponentsCustomizerProvider implements ProjectCustomizer.CompositeCategoryProvider { + + public static final String COMPONENTS_CUSTOMIZER = "components_customizer"; // NOI18N + + @Override + public ProjectCustomizer.Category createCategory(Lookup lkp) { + return ProjectCustomizer.Category.create(COMPONENTS_CUSTOMIZER, + NbBundle.getMessage(BladeComponentsCustomizerProvider.class, + "LBL_ComponentsConfig"), null); + } + + @Override + public JComponent createComponent(ProjectCustomizer.Category category, Lookup context) { + Project project = context.lookup(Project.class); + assert project != null; + + BladeComponents panel = new BladeComponents(project); + category.setOkButtonListener(new Listener(panel)); + return panel; + } + + private class Listener implements ActionListener { + private final BladeComponents panel; + public Listener(BladeComponents panel){ + this.panel = panel; + } + @Override + public void actionPerformed(ActionEvent e) { + this.panel.storeData(); + } + + } +} diff --git a/php/php.blade/src/org/netbeans/modules/php/blade/editor/ui/customizer/BladeDirectives.form b/php/php.blade/src/org/netbeans/modules/php/blade/editor/ui/customizer/BladeDirectives.form new file mode 100644 index 000000000000..fdee705ee27b --- /dev/null +++ b/php/php.blade/src/org/netbeans/modules/php/blade/editor/ui/customizer/BladeDirectives.form @@ -0,0 +1,157 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/php/php.blade/src/org/netbeans/modules/php/blade/editor/ui/customizer/BladeDirectives.java b/php/php.blade/src/org/netbeans/modules/php/blade/editor/ui/customizer/BladeDirectives.java new file mode 100644 index 000000000000..e98d5d043f08 --- /dev/null +++ b/php/php.blade/src/org/netbeans/modules/php/blade/editor/ui/customizer/BladeDirectives.java @@ -0,0 +1,196 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.php.blade.editor.ui.customizer; + +import java.awt.Desktop; +import java.awt.EventQueue; +import java.io.File; +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; +import org.netbeans.api.project.Project; +import org.netbeans.modules.php.blade.editor.directives.CustomDirectives; +import org.netbeans.modules.php.blade.project.BladeProjectProperties; +import org.openide.filesystems.FileChooserBuilder; +import org.openide.filesystems.FileUtil; +import org.openide.util.Exceptions; + +/** + * + * @author bhaidu + */ +public class BladeDirectives extends javax.swing.JPanel { + + private final Project project; + private final BladeProjectProperties bladeProperties; + + /** + * Creates new form CustomizerIncludePath + * + * @param project + */ + public BladeDirectives(Project project) { + this.project = project; + bladeProperties = BladeProjectProperties.getInstance(project); + initComponents(); + init(); + } + + private void init() { + //the model reference is enough + customDirectivePathList.setModel(bladeProperties.getModelForDirectiveCusomizerPathList()); + } + + public void storeData() { + bladeProperties.storeDirectiveCustomizerPaths(); + CustomDirectives.resetInstance(project); + } + + /** + * This method is called from within the constructor to initialize the form. + * WARNING: Do NOT modify this code. The content of this method is always + * regenerated by the Form Editor. + */ + @SuppressWarnings("unchecked") + // //GEN-BEGIN:initComponents + private void initComponents() { + + includePathLabel = new javax.swing.JLabel(); + compilerPathFileButton = new javax.swing.JButton(); + jScrollPane1 = new javax.swing.JScrollPane(); + customDirectivePathList = new javax.swing.JList<>(); + removePathButton = new javax.swing.JButton(); + jLabel1 = new javax.swing.JLabel(); + jLabel3 = new javax.swing.JLabel(); + + org.openide.awt.Mnemonics.setLocalizedText(includePathLabel, org.openide.util.NbBundle.getMessage(BladeDirectives.class, "BladeDirectives.includePathLabel.text_1")); // NOI18N + + org.openide.awt.Mnemonics.setLocalizedText(compilerPathFileButton, org.openide.util.NbBundle.getMessage(BladeDirectives.class, "BladeDirectives.compilerPathFileButton.text_1")); // NOI18N + compilerPathFileButton.setToolTipText(org.openide.util.NbBundle.getMessage(BladeDirectives.class, "BladeDirectives.compilerPathFileButton.toolTipText_1")); // NOI18N + compilerPathFileButton.setActionCommand(org.openide.util.NbBundle.getMessage(BladeDirectives.class, "BladeDirectives.compilerPathFileButton.actionCommand_1")); // NOI18N + compilerPathFileButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + compilerPathFileButtonActionPerformed(evt); + } + }); + + jScrollPane1.setViewportView(customDirectivePathList); + + org.openide.awt.Mnemonics.setLocalizedText(removePathButton, org.openide.util.NbBundle.getMessage(BladeDirectives.class, "BladeDirectives.removePathButton.text_1")); // NOI18N + removePathButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + removePathButtonActionPerformed(evt); + } + }); + + org.openide.awt.Mnemonics.setLocalizedText(jLabel1, org.openide.util.NbBundle.getMessage(BladeDirectives.class, "BladeDirectives.jLabel1.text")); // NOI18N + + jLabel3.setForeground(new java.awt.Color(0, 51, 255)); + org.openide.awt.Mnemonics.setLocalizedText(jLabel3, org.openide.util.NbBundle.getMessage(BladeDirectives.class, "BladeDirectives.jLabel3.text")); // NOI18N + jLabel3.addMouseListener(new java.awt.event.MouseAdapter() { + public void mouseClicked(java.awt.event.MouseEvent evt) { + jLabel3MouseClicked(evt); + } + }); + + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); + this.setLayout(layout); + layout.setHorizontalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addContainerGap() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() + .addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 486, Short.MAX_VALUE) + .addGap(18, 18, 18) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false) + .addComponent(compilerPathFileButton, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(removePathButton, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))) + .addGroup(layout.createSequentialGroup() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(includePathLabel) + .addGroup(layout.createSequentialGroup() + .addGap(10, 10, 10) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(jLabel3) + .addComponent(jLabel1)))) + .addGap(0, 306, Short.MAX_VALUE))) + .addContainerGap()) + ); + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addGap(20, 20, 20) + .addComponent(includePathLabel) + .addGap(15, 15, 15) + .addComponent(jLabel1) + .addGap(8, 8, 8) + .addComponent(jLabel3) + .addGap(18, 18, 18) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(jScrollPane1, javax.swing.GroupLayout.PREFERRED_SIZE, 143, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGroup(layout.createSequentialGroup() + .addComponent(compilerPathFileButton) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(removePathButton))) + .addGap(0, 50, Short.MAX_VALUE)) + ); + }// //GEN-END:initComponents + + private void compilerPathFileButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_compilerPathFileButtonActionPerformed + assert EventQueue.isDispatchThread(); + File sources = new FileChooserBuilder(BladeDirectives.class) + .setFilesOnly(true) + .setTitle("Select compiler File Path") + .setDefaultWorkingDirectory(FileUtil.toFile(project.getProjectDirectory())) + .forceUseOfDefaultWorkingDirectory(true) + .showOpenDialog(); + if (sources != null) { + //TODO validate the path if it has directives + bladeProperties.addDirectiveCustomizerPath(FileUtil.normalizeFile(sources).getAbsolutePath()); + } + }//GEN-LAST:event_compilerPathFileButtonActionPerformed + + private void removePathButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_removePathButtonActionPerformed + int index = customDirectivePathList.getSelectedIndex(); + if (index > -1) { + bladeProperties.removeCustomizerPath(index); + } + + }//GEN-LAST:event_removePathButtonActionPerformed + + private void jLabel3MouseClicked(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_jLabel3MouseClicked + try { + Desktop.getDesktop().browse(new URI("https://laravel.com/docs/10.x/blade#extending-blade")); // NOI18N + } catch (URISyntaxException | IOException ex) { + Exceptions.printStackTrace(ex); + } + + }//GEN-LAST:event_jLabel3MouseClicked + + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JButton compilerPathFileButton; + private javax.swing.JList customDirectivePathList; + private javax.swing.JLabel includePathLabel; + private javax.swing.JLabel jLabel1; + private javax.swing.JLabel jLabel3; + private javax.swing.JScrollPane jScrollPane1; + private javax.swing.JButton removePathButton; + // End of variables declaration//GEN-END:variables +} diff --git a/php/php.blade/src/org/netbeans/modules/php/blade/editor/ui/customizer/BladeDirectivesCustomizerProvider.java b/php/php.blade/src/org/netbeans/modules/php/blade/editor/ui/customizer/BladeDirectivesCustomizerProvider.java new file mode 100644 index 000000000000..7a8dd8fe2ab1 --- /dev/null +++ b/php/php.blade/src/org/netbeans/modules/php/blade/editor/ui/customizer/BladeDirectivesCustomizerProvider.java @@ -0,0 +1,67 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.php.blade.editor.ui.customizer; + +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import javax.swing.JComponent; +import org.netbeans.api.project.Project; +import org.netbeans.spi.project.ui.support.ProjectCustomizer; +import org.openide.util.Lookup; +import org.openide.util.NbBundle; + +/** + * + * @author bhaidu + */ +public class BladeDirectivesCustomizerProvider implements ProjectCustomizer.CompositeCategoryProvider { + + public static final String BLADE_DIRECTIVES = "Custom Directives"; // NOI18N + + @NbBundle.Messages("BladeCompilerCustomizerProvider.name=Blade Directives") + @Override + public ProjectCustomizer.Category createCategory(Lookup lkp) { + return ProjectCustomizer.Category.create(BLADE_DIRECTIVES, BLADE_DIRECTIVES, null); + } + + @Override + public JComponent createComponent(ProjectCustomizer.Category category, Lookup context) { + Project project = context.lookup(Project.class); + assert project != null; + + BladeDirectives panel = new BladeDirectives(project); + category.setOkButtonListener(new BladeDirectivesCustomizerProvider.Listener(panel)); + return panel; + } + + private class Listener implements ActionListener { + + private final BladeDirectives panel; + + public Listener(BladeDirectives panel) { + this.panel = panel; + } + + @Override + public void actionPerformed(ActionEvent e) { + this.panel.storeData(); + } + + } +} diff --git a/php/php.blade/src/org/netbeans/modules/php/blade/editor/ui/customizer/BladeGeneralSettings.form b/php/php.blade/src/org/netbeans/modules/php/blade/editor/ui/customizer/BladeGeneralSettings.form new file mode 100644 index 000000000000..6b535800c4d6 --- /dev/null +++ b/php/php.blade/src/org/netbeans/modules/php/blade/editor/ui/customizer/BladeGeneralSettings.form @@ -0,0 +1,83 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/php/php.blade/src/org/netbeans/modules/php/blade/editor/ui/customizer/BladeGeneralSettings.java b/php/php.blade/src/org/netbeans/modules/php/blade/editor/ui/customizer/BladeGeneralSettings.java new file mode 100644 index 000000000000..18ed2f2a02f2 --- /dev/null +++ b/php/php.blade/src/org/netbeans/modules/php/blade/editor/ui/customizer/BladeGeneralSettings.java @@ -0,0 +1,98 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.php.blade.editor.ui.customizer; + +import org.netbeans.api.project.Project; +import org.netbeans.modules.php.blade.editor.preferences.ModulePreferences; +import org.netbeans.modules.php.blade.editor.preferences.GeneralPreferencesUtils; +/** + * + * @author bhaidu + */ +public class BladeGeneralSettings extends javax.swing.JPanel { + + /** + * Creates new form BladeGeneral + * @param project + */ + public BladeGeneralSettings(Project project) { + initComponents(); + init(); + } + + private void init() { + this.formatting_enabled.setSelected(GeneralPreferencesUtils.isFormattingEnabled()); + this.indentation_enabled.setSelected(GeneralPreferencesUtils.isIndentationEnabled()); + } + /** + * This method is called from within the constructor to initialize the form. + * WARNING: Do NOT modify this code. The content of this method is always + * regenerated by the Form Editor. + */ + @SuppressWarnings("unchecked") + // //GEN-BEGIN:initComponents + private void initComponents() { + + jLabel1 = new javax.swing.JLabel(); + formatting_enabled = new javax.swing.JCheckBox(); + indentation_enabled = new javax.swing.JCheckBox(); + + org.openide.awt.Mnemonics.setLocalizedText(jLabel1, org.openide.util.NbBundle.getMessage(BladeGeneralSettings.class, "BladeGeneralSettings.jLabel1.text")); // NOI18N + + org.openide.awt.Mnemonics.setLocalizedText(formatting_enabled, org.openide.util.NbBundle.getMessage(BladeGeneralSettings.class, "BladeGeneralSettings.formatting_enabled.text")); // NOI18N + + org.openide.awt.Mnemonics.setLocalizedText(indentation_enabled, org.openide.util.NbBundle.getMessage(BladeGeneralSettings.class, "BladeGeneralSettings.indentation_enabled.text")); // NOI18N + + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); + this.setLayout(layout); + layout.setHorizontalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addContainerGap() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(formatting_enabled) + .addComponent(jLabel1) + .addComponent(indentation_enabled)) + .addContainerGap(184, Short.MAX_VALUE)) + ); + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addComponent(jLabel1) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(formatting_enabled) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(indentation_enabled) + .addGap(0, 229, Short.MAX_VALUE)) + ); + }// //GEN-END:initComponents + + + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JCheckBox formatting_enabled; + private javax.swing.JCheckBox indentation_enabled; + private javax.swing.JLabel jLabel1; + // End of variables declaration//GEN-END:variables + + public void storeData() { + ModulePreferences.setPrefBoolean(GeneralPreferencesUtils.ENABLE_FORMATTING, this.formatting_enabled.isSelected()); + ModulePreferences.setPrefBoolean(GeneralPreferencesUtils.ENABLE_INDENTATION, this.indentation_enabled.isSelected()); + } + +} diff --git a/php/php.blade/src/org/netbeans/modules/php/blade/editor/ui/customizer/BladeOptionsCustomizerProvider.java b/php/php.blade/src/org/netbeans/modules/php/blade/editor/ui/customizer/BladeOptionsCustomizerProvider.java new file mode 100644 index 000000000000..c46e7ae390ec --- /dev/null +++ b/php/php.blade/src/org/netbeans/modules/php/blade/editor/ui/customizer/BladeOptionsCustomizerProvider.java @@ -0,0 +1,65 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.php.blade.editor.ui.customizer; + +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import javax.swing.JComponent; +import org.netbeans.api.project.Project; +import org.netbeans.spi.project.ui.support.ProjectCustomizer; +import org.openide.util.Lookup; +import org.openide.util.NbBundle; + +/** + * + * @author bhaidu + */ +public class BladeOptionsCustomizerProvider implements ProjectCustomizer.CompositeCategoryProvider { + + public static final String VIEWS_FOLDERS = "views_folders"; // NOI18N + + @Override + public ProjectCustomizer.Category createCategory(Lookup lkp) { + return ProjectCustomizer.Category.create(VIEWS_FOLDERS, + NbBundle.getMessage(BladeOptionsCustomizerProvider.class, + "LBL_ViewsFolders"), null); + } + + @Override + public JComponent createComponent(ProjectCustomizer.Category category, Lookup context) { + Project project = context.lookup(Project.class); + assert project != null; + + BladeViewsFoldersPanel panel = new BladeViewsFoldersPanel(project); + category.setOkButtonListener(new Listener(panel)); + return panel; + } + + private class Listener implements ActionListener { + private final BladeViewsFoldersPanel panel; + public Listener(BladeViewsFoldersPanel panel){ + this.panel = panel; + } + @Override + public void actionPerformed(ActionEvent e) { + this.panel.storeData(); + } + + } +} diff --git a/php/php.blade/src/org/netbeans/modules/php/blade/editor/ui/customizer/BladeSettingsCustomizerProvider.java b/php/php.blade/src/org/netbeans/modules/php/blade/editor/ui/customizer/BladeSettingsCustomizerProvider.java new file mode 100644 index 000000000000..d775ba100f3d --- /dev/null +++ b/php/php.blade/src/org/netbeans/modules/php/blade/editor/ui/customizer/BladeSettingsCustomizerProvider.java @@ -0,0 +1,113 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.php.blade.editor.ui.customizer; + +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.util.ArrayList; +import java.util.List; +import javax.swing.JComponent; +import org.netbeans.api.project.Project; +import org.netbeans.spi.project.ui.support.ProjectCustomizer; +import org.openide.util.Lookup; +import org.openide.util.NbBundle; + +/** + * Blade settings node + * + * @author bhaidu + */ +public class BladeSettingsCustomizerProvider implements ProjectCustomizer.CompositeCategoryProvider { + + public static final String CUSTOMIZER_IDENT = "Laravel Blade"; // NOI18N + + @NbBundle.Messages("BladeSettingsCustomizerProvider.name=Blade") + @Override + public ProjectCustomizer.Category createCategory(Lookup lkp) { + List subcategories = new ArrayList<>(); + BladeOptionsCustomizerProvider optionsCustomizer = new BladeOptionsCustomizerProvider(); + subcategories.add(optionsCustomizer.createCategory(lkp)); + BladeDirectivesCustomizerProvider directiveCustomizer = new BladeDirectivesCustomizerProvider(); + subcategories.add(directiveCustomizer.createCategory(lkp)); + BladeComponentsCustomizerProvider bladeComponentsCustomizer = new BladeComponentsCustomizerProvider(); + subcategories.add(bladeComponentsCustomizer.createCategory(lkp)); + return ProjectCustomizer.Category.create(CUSTOMIZER_IDENT, + NbBundle.getMessage(BladeSettingsCustomizerProvider.class, "LBL_LaravelBlade"), null, + subcategories.toArray(ProjectCustomizer.Category[]::new)); + } + + @Override + public JComponent createComponent(ProjectCustomizer.Category category, Lookup context) { + switch (category.getName()) { + case BladeOptionsCustomizerProvider.VIEWS_FOLDERS -> { + BladeOptionsCustomizerProvider provider = new BladeOptionsCustomizerProvider(); + return provider.createComponent(category, context); + } + case BladeDirectivesCustomizerProvider.BLADE_DIRECTIVES -> { + BladeDirectivesCustomizerProvider directivesProvider = new BladeDirectivesCustomizerProvider(); + return directivesProvider.createComponent(category, context); + } + case BladeComponentsCustomizerProvider.COMPONENTS_CUSTOMIZER -> { + BladeComponentsCustomizerProvider bladeComponentsCustomizer = new BladeComponentsCustomizerProvider(); + return bladeComponentsCustomizer.createComponent(category, context); + } + } + + return createGeneralSettingsComponent(category, context); + } + + public JComponent createGeneralSettingsComponent(ProjectCustomizer.Category category, Lookup context) { + Project project = context.lookup(Project.class); + assert project != null; + + BladeGeneralSettings panel = new BladeGeneralSettings(project); + category.setOkButtonListener(new Listener(panel)); + return panel; + } + + private class Listener implements ActionListener { + + private final BladeGeneralSettings panel; + + public Listener(BladeGeneralSettings panel) { + this.panel = panel; + } + + @Override + public void actionPerformed(ActionEvent e) { + this.panel.storeData(); + } + + } + + @ProjectCustomizer.CompositeCategoryProvider.Registration( + projectType = "org.netbeans.modules.web.clientproject", // NOI18N + position = 367) + public static BladeSettingsCustomizerProvider forHtml5Project() { + return new BladeSettingsCustomizerProvider(); + } + + @ProjectCustomizer.CompositeCategoryProvider.Registration( + projectType = "org-netbeans-modules-php-project", // NOI18N + position = 402) + public static ProjectCustomizer.CompositeCategoryProvider forPhpProject() { + return new BladeSettingsCustomizerProvider(); + } + +} diff --git a/php/php.blade/src/org/netbeans/modules/php/blade/editor/ui/customizer/BladeViewsFoldersPanel.form b/php/php.blade/src/org/netbeans/modules/php/blade/editor/ui/customizer/BladeViewsFoldersPanel.form new file mode 100644 index 000000000000..0d1b60f1b72a --- /dev/null +++ b/php/php.blade/src/org/netbeans/modules/php/blade/editor/ui/customizer/BladeViewsFoldersPanel.form @@ -0,0 +1,186 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/php/php.blade/src/org/netbeans/modules/php/blade/editor/ui/customizer/BladeViewsFoldersPanel.java b/php/php.blade/src/org/netbeans/modules/php/blade/editor/ui/customizer/BladeViewsFoldersPanel.java new file mode 100644 index 000000000000..ccf258fdd723 --- /dev/null +++ b/php/php.blade/src/org/netbeans/modules/php/blade/editor/ui/customizer/BladeViewsFoldersPanel.java @@ -0,0 +1,228 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.php.blade.editor.ui.customizer; + +import java.awt.EventQueue; +import java.io.File; +import javax.swing.event.ChangeListener; +import org.netbeans.api.project.Project; +import org.netbeans.modules.php.blade.editor.indexing.IndexManager; +import org.netbeans.modules.php.blade.project.BladeProjectProperties; +import org.netbeans.spi.options.OptionsPanelController; +import org.openide.filesystems.*; +import org.openide.util.ChangeSupport; +import org.openide.util.NbBundle; + +@NbBundle.Messages("BladePanel.options.keywords.tabTitle=Frameworks & Tools") +@OptionsPanelController.Keywords( + keywords = { + "php", "blade" // NOI18N + }, + location = "Html5", // NOI18N + tabTitle = "#BladePanel.options.keywords.tabTitle" // NOI18N +) + +public final class BladeViewsFoldersPanel extends javax.swing.JPanel { + + private final ChangeSupport changeSupport = new ChangeSupport(this); + private final Project project; + private final BladeProjectProperties bladeProperties; + + BladeViewsFoldersPanel(Project project) { + assert project != null; + this.project = project; + bladeProperties = BladeProjectProperties.getInstance(project); + initComponents(); + init(); + } + + private void init(){ + viewsPathList.setModel(bladeProperties.getModelViewsPathList()); + nonLaravelViewDeclFinder.setSelected(bladeProperties.getNonLaravelDeclFinderFlag()); + } + + public void storeData(){ + bladeProperties.storeViewsPaths(); + bladeProperties.storeNonLaravelDeclFinderFlag(nonLaravelViewDeclFinder.isSelected()); + } + + public void addChangeListener(ChangeListener listener) { + changeSupport.addChangeListener(listener); + } + + /** + * This method is called from within the constructor to initialize the form. + * WARNING: Do NOT modify this code. The content of this method is always + * regenerated by the Form Editor. + */ + // //GEN-BEGIN:initComponents + private void initComponents() { + + toggleCommentButtonGroup = new javax.swing.ButtonGroup(); + jLabel1 = new javax.swing.JLabel(); + jScrollPane1 = new javax.swing.JScrollPane(); + viewsPathList = new javax.swing.JList<>(); + addViewFolderButton = new javax.swing.JButton(); + removeViewFolderButton = new javax.swing.JButton(); + reindexViewsButton = new javax.swing.JButton(); + jLabel3 = new javax.swing.JLabel(); + jLabel4 = new javax.swing.JLabel(); + reindexViewFolderButton = new javax.swing.JButton(); + nonLaravelViewDeclFinder = new javax.swing.JCheckBox(); + + org.openide.awt.Mnemonics.setLocalizedText(jLabel1, org.openide.util.NbBundle.getMessage(BladeViewsFoldersPanel.class, "BladeViewsFoldersPanel.jLabel1.text")); // NOI18N + jLabel1.setToolTipText(org.openide.util.NbBundle.getMessage(BladeViewsFoldersPanel.class, "BladeViewsFoldersPanel.jLabel1.toolTipText")); // NOI18N + + jScrollPane1.setViewportView(viewsPathList); + + org.openide.awt.Mnemonics.setLocalizedText(addViewFolderButton, org.openide.util.NbBundle.getMessage(BladeViewsFoldersPanel.class, "BladeViewsFoldersPanel.addViewFolderButton.text")); // NOI18N + addViewFolderButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + addViewFolderButtonActionPerformed(evt); + } + }); + + org.openide.awt.Mnemonics.setLocalizedText(removeViewFolderButton, org.openide.util.NbBundle.getMessage(BladeViewsFoldersPanel.class, "BladeViewsFoldersPanel.removeViewFolderButton.text")); // NOI18N + removeViewFolderButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + removeViewFolderButtonActionPerformed(evt); + } + }); + + org.openide.awt.Mnemonics.setLocalizedText(reindexViewsButton, org.openide.util.NbBundle.getMessage(BladeViewsFoldersPanel.class, "BladeViewsFoldersPanel.reindexViewsButton.text")); // NOI18N + reindexViewsButton.setToolTipText(org.openide.util.NbBundle.getMessage(BladeViewsFoldersPanel.class, "BladeViewsFoldersPanel.reindexViewsButton.toolTipText")); // NOI18N + reindexViewsButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + reindexViewsButtonActionPerformed(evt); + } + }); + + org.openide.awt.Mnemonics.setLocalizedText(jLabel3, org.openide.util.NbBundle.getMessage(BladeViewsFoldersPanel.class, "BladeViewsFoldersPanel.jLabel3.text")); // NOI18N + + org.openide.awt.Mnemonics.setLocalizedText(jLabel4, org.openide.util.NbBundle.getMessage(BladeViewsFoldersPanel.class, "BladeViewsFoldersPanel.jLabel4.text")); // NOI18N + + org.openide.awt.Mnemonics.setLocalizedText(reindexViewFolderButton, org.openide.util.NbBundle.getMessage(BladeViewsFoldersPanel.class, "BladeViewsFoldersPanel.reindexViewFolderButton.text")); // NOI18N + reindexViewFolderButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + reindexViewFolderButtonActionPerformed(evt); + } + }); + + org.openide.awt.Mnemonics.setLocalizedText(nonLaravelViewDeclFinder, org.openide.util.NbBundle.getMessage(BladeViewsFoldersPanel.class, "BladeViewsFoldersPanel.nonLaravelViewDeclFinder.text")); // NOI18N + + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); + this.setLayout(layout); + layout.setHorizontalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addContainerGap() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(jLabel3, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addGroup(layout.createSequentialGroup() + .addComponent(jScrollPane1) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false) + .addComponent(removeViewFolderButton, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(addViewFolderButton, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(reindexViewFolderButton, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))) + .addGroup(layout.createSequentialGroup() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(jLabel1) + .addComponent(reindexViewsButton) + .addComponent(jLabel4) + .addComponent(nonLaravelViewDeclFinder)) + .addGap(0, 83, Short.MAX_VALUE))) + .addContainerGap()) + ); + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addContainerGap() + .addComponent(jLabel1) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jLabel3, javax.swing.GroupLayout.PREFERRED_SIZE, 20, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jLabel4) + .addGap(18, 18, 18) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addComponent(addViewFolderButton) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(removeViewFolderButton) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(reindexViewFolderButton)) + .addComponent(jScrollPane1, javax.swing.GroupLayout.PREFERRED_SIZE, 125, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(reindexViewsButton) + .addGap(18, 18, 18) + .addComponent(nonLaravelViewDeclFinder) + .addContainerGap(144, Short.MAX_VALUE)) + ); + }// //GEN-END:initComponents + + private void addViewFolderButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_addViewFolderButtonActionPerformed + assert EventQueue.isDispatchThread(); + File sources = new FileChooserBuilder(BladeDirectives.class) + .setDirectoriesOnly(true) + .setTitle("Select View Folder Path") + .setDefaultWorkingDirectory(FileUtil.toFile(project.getProjectDirectory())) + .forceUseOfDefaultWorkingDirectory(true) + .showOpenDialog(); + if (sources != null) { + //TODO validate the path if it has blade files ? + bladeProperties.addViewsPath(FileUtil.normalizeFile(sources).getAbsolutePath()); + } + }//GEN-LAST:event_addViewFolderButtonActionPerformed + + private void removeViewFolderButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_removeViewFolderButtonActionPerformed + int index = viewsPathList.getSelectedIndex(); + if (index > -1) { + bladeProperties.removeViewsPath(index); + } + }//GEN-LAST:event_removeViewFolderButtonActionPerformed + + private void reindexViewsButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_reindexViewsButtonActionPerformed + IndexManager.reindexProjectViews(project); + }//GEN-LAST:event_reindexViewsButtonActionPerformed + + private void reindexViewFolderButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_reindexViewFolderButtonActionPerformed + String path = viewsPathList.getSelectedValue(); + if (path != null && path.length() > 0) { + File viewPath = new File(path); + if (viewPath.exists()) { + IndexManager.reindexFolder(viewPath); + } + } + }//GEN-LAST:event_reindexViewFolderButtonActionPerformed + + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JButton addViewFolderButton; + private javax.swing.JLabel jLabel1; + private javax.swing.JLabel jLabel3; + private javax.swing.JLabel jLabel4; + private javax.swing.JScrollPane jScrollPane1; + private javax.swing.JCheckBox nonLaravelViewDeclFinder; + private javax.swing.JButton reindexViewFolderButton; + private javax.swing.JButton reindexViewsButton; + private javax.swing.JButton removeViewFolderButton; + private javax.swing.ButtonGroup toggleCommentButtonGroup; + private javax.swing.JList viewsPathList; + // End of variables declaration//GEN-END:variables + +} diff --git a/php/php.blade/src/org/netbeans/modules/php/blade/editor/ui/customizer/Bundle.properties b/php/php.blade/src/org/netbeans/modules/php/blade/editor/ui/customizer/Bundle.properties new file mode 100644 index 000000000000..854ab547ae6b --- /dev/null +++ b/php/php.blade/src/org/netbeans/modules/php/blade/editor/ui/customizer/Bundle.properties @@ -0,0 +1,62 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +BladeDirectives.removePathButton.text=Remove File +BladeDirectives.compilerPathFileButton.actionCommand=compilerPathFolderButton +BladeDirectives.compilerPathFileButton.toolTipText= +BladeDirectives.compilerPathFileButton.text=Add File + + +# include path +BladeDirectives.includePathLabel.text=Custom Directive paths : +BladeDirectives.removePathButton.text_1=Remove File +BladeDirectives.compilerPathFileButton.actionCommand_1=compilerPathFolderButton +BladeDirectives.compilerPathFileButton.toolTipText_1= +BladeDirectives.compilerPathFileButton.text_1=Add File + + +# include path +BladeDirectives.includePathLabel.text_1=Custom Directive php setup files +LBL_LaravelBlade=Laravel blade +LBL_ViewsFolders=Views Folders +LBL_ComponentsConfig=Blade Components Config +BladeDirectives.jLabel1.text=Php files containing custom directive defintions as in : +BladeDirectives.jLabel3.text=https://laravel.com/docs/10.x/blade#extending-blade +BladeViewsFoldersPanel.reindexViewFolderButton.text=Reindex selected +BladeViewsFoldersPanel.jLabel4.text=*Also add root folders of custom folder and fallback configuration (ex: views/template2022/) +BladeViewsFoldersPanel.jLabel3.text=*Add the folder where the blade view files are used. (ex: {root}/views) +BladeViewsFoldersPanel.reindexViewsButton.toolTipText=Reindex blade files to rebuild index regarding view paths +BladeViewsFoldersPanel.reindexViewsButton.text=Re-index views +BladeViewsFoldersPanel.removeViewFolderButton.text=Remove Folder +BladeViewsFoldersPanel.addViewFolderButton.text=Add Folder +BladeViewsFoldersPanel.jLabel1.toolTipText=Add your folder containing your blade files. +BladeViewsFoldersPanel.jLabel1.text=Views Folders +BladeGeneralSettings.jLabel1.text=Blade General Settings +BladeGeneralSettings.formatting_enabled.text=Enable Formatting (experimental) +BladeGeneralSettings.indentation_enabled.text=Enable Indentation (experimental) +BladeGeneralSettings.auto_tag_completion.text=Auto tag completion ("{{ }}", "{!! !!}", "{{-- --}}") +BladeViewsFoldersPanel.nonLaravelViewDeclFinder.text=Allow Go to click action for paths arugment on `view`, `render` and `make` method +BladeGeneralSettings.jLabel2.text=Global settings (all php projects) + + +# include path +BladeComponents.includePathLabel.text_1=Custom Blade components folders list +BladeComponents.removePathButton.text_1=Remove Folder +BladeComponents.compilerPathFileButton.actionCommand_1=compilerPathFolderButton +BladeComponents.compilerPathFileButton.toolTipText_1= +BladeComponents.compilerPathFileButton.text_1=Add Folder +BladeComponents.jLabel1.text=Add folders containing class components, for autocomplete & declaration finder on " list) { + List result = new ArrayList<>(); + while (list.hasMoreElements()) { + result.add(list.nextElement()); + } + + return String.join(FILE_ITEM_SEPARATOR, result); + } +} diff --git a/php/php.blade/src/org/netbeans/modules/php/blade/editor/ui/wizard/NewFileWizardIterator.java b/php/php.blade/src/org/netbeans/modules/php/blade/editor/ui/wizard/NewFileWizardIterator.java new file mode 100644 index 000000000000..04d36d21b5ee --- /dev/null +++ b/php/php.blade/src/org/netbeans/modules/php/blade/editor/ui/wizard/NewFileWizardIterator.java @@ -0,0 +1,137 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.php.blade.editor.ui.wizard; + +import java.io.IOException; +import java.util.Collections; +import java.util.Set; +import javax.swing.event.ChangeListener; +import org.netbeans.api.project.Project; +import org.netbeans.api.project.ProjectUtils; +import org.netbeans.api.project.SourceGroup; +import org.netbeans.api.project.Sources; +import org.netbeans.api.templates.TemplateRegistration; +import org.netbeans.modules.php.blade.editor.BladeLanguage; +import org.openide.WizardDescriptor; +import org.openide.filesystems.FileObject; +import org.openide.loaders.DataFolder; +import org.netbeans.spi.project.ui.templates.support.Templates; +import org.openide.loaders.DataObject; + +/** + * + * @author bhaidu + */ +public class NewFileWizardIterator implements WizardDescriptor.InstantiatingIterator { + + private WizardDescriptor wizard; + public static final String DEFAULT_FILENAME = "myfile"; //NOI18N + private WizardDescriptor.Panel wizardPanel; + + private NewFileWizardIterator() { + } + + @TemplateRegistration(folder = "Blade", category = "PHP", + content = "../../../resources/emptyBladeFile.blade.php", + description = "../../../resources/NewBladeFileDescription.html", + position = 120, + displayName = "Blade file", + scriptEngine = "freemarker") + public static WizardDescriptor.InstantiatingIterator createBladeWizardIterator() { + return new NewFileWizardIterator(); + } + + @Override + public Set instantiate() throws IOException { + FileObject dir = Templates.getTargetFolder(wizard); + FileObject template = Templates.getTemplate(wizard); + + DataFolder dataFolder = DataFolder.findFolder(dir); + DataObject dataTemplate = DataObject.find(template); + DataObject createdFile = dataTemplate.createFromTemplate(dataFolder, Templates.getTargetName(wizard) + BladeLanguage.FILE_EXTENSION_SUFFIX); + return Collections.singleton(createdFile.getPrimaryFile()); + } + + @Override + public void initialize(WizardDescriptor wizard) { + this.wizard = wizard; + Templates.setTargetName(wizard, DEFAULT_FILENAME); + wizardPanel = createWizardPanel(); + } + + @Override + public void uninitialize(WizardDescriptor wizard) { + this.wizard = null; + wizardPanel = null; + } + + @Override + public WizardDescriptor.Panel current() { + return wizardPanel; + } + + @Override + public String name() { + return "new blade php file wizard"; // NOI18N + } + + @Override + public boolean hasNext() { + return false; + } + + @Override + public boolean hasPrevious() { + return false; + } + + @Override + public void nextPanel() { + } + + @Override + public void previousPanel() { + } + + @Override + public void addChangeListener(ChangeListener listener) { + } + + @Override + public void removeChangeListener(ChangeListener listener) { + } + + + private WizardDescriptor.Panel createWizardPanel() { + Project project = getProject(); + assert project != null; + return Templates.buildSimpleTargetChooser(project, getSourceGroups(project)) + .create(); + } + + private SourceGroup[] getSourceGroups(Project project) { + Sources sources = ProjectUtils.getSources(project); + return sources.getSourceGroups(Sources.TYPE_GENERIC); + } + + private Project getProject() { + return Templates.getProject(wizard); + } + +} diff --git a/php/php.blade/src/org/netbeans/modules/php/blade/project/AssetsBundlerSupport.java b/php/php.blade/src/org/netbeans/modules/php/blade/project/AssetsBundlerSupport.java new file mode 100644 index 000000000000..a665a8ba4549 --- /dev/null +++ b/php/php.blade/src/org/netbeans/modules/php/blade/project/AssetsBundlerSupport.java @@ -0,0 +1,31 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.php.blade.project; + +/** + * + * @author bogdan + */ +public class AssetsBundlerSupport { + + public static final String RESOURCE_ROOT = "resources"; // NOI18N + public static final String JS_ASSET_FOLDER = RESOURCE_ROOT + "/js"; // NOI18N + public static final String CSS_ASSET_FOLDER = RESOURCE_ROOT + "/css"; // NOI18N + +} diff --git a/php/php.blade/src/org/netbeans/modules/php/blade/project/BladeProjectProperties.java b/php/php.blade/src/org/netbeans/modules/php/blade/project/BladeProjectProperties.java new file mode 100644 index 000000000000..95cf39d454bd --- /dev/null +++ b/php/php.blade/src/org/netbeans/modules/php/blade/project/BladeProjectProperties.java @@ -0,0 +1,225 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.php.blade.project; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.prefs.PreferenceChangeListener; +import java.util.prefs.Preferences; +import javax.swing.DefaultListModel; +import org.netbeans.api.project.Project; +import org.netbeans.api.project.ProjectUtils; +import org.netbeans.modules.php.blade.editor.ui.customizer.UIOptionsUtils; +import org.openide.util.NbPreferences; + +/** + * @todo ADD NEW OPTION VALUES + * @todo use nodes for files + * + * @author Haidu Bogdan + */ +public final class BladeProjectProperties { + + private static final Map INSTANCES = new HashMap<>(); + private static final String DIRECTIVE_CUSTOMIZER_PATH_LIST = "directive_customizer.path.list"; // NOI18N + private static final String VIEW_PATH_LIST = "views.path.list"; // NOI18N + private static final String BLADE_COMPONENT_CLASS_FOLDER_LIST = "blade_component_class.folder.list"; // NOI18N + private static final String NON_LARAVEL_DECL_FINDER = "non_laravel.decl.finder"; // NOI18N + private final Project project; + + private DefaultListModel directiveCustomizerPathList = new DefaultListModel(); + private DefaultListModel viewsPathList = new DefaultListModel(); + private DefaultListModel bladeComponentsClassFolderList = new DefaultListModel(); + // enable declaration finder outside of framework plugin + private final AtomicBoolean nonLaravelDeclFinder = new AtomicBoolean(false); + // the pipe "|" char needs to be escaped + public static final String ESCAPED_VIEW_PATH_SEPARATOR = "\\|"; // NOI18N + + private BladeProjectProperties(Project project) { + this.project = project; + initModelsFromPreferences(); + } + + public static BladeProjectProperties getInstance(Project project) { + synchronized (INSTANCES) { + if (INSTANCES.containsKey(project)) { + return INSTANCES.get(project); + } + BladeProjectProperties instance = new BladeProjectProperties(project); + INSTANCES.put(project, instance); + return instance; + } + } + + public static void closeProject(Project project) { + synchronized (INSTANCES) { + INSTANCES.remove(project); + } + } + + private Preferences getPreferences() { + if (project != null) { + return ProjectUtils.getPreferences(project, this.getClass(), false); + } + return NbPreferences.forModule(this.getClass()); + } + + private void initModelsFromPreferences() { + directiveCustomizerPathList = createModelForDirectiveCusomizerPathList(); + viewsPathList = createModelForViewsPathList(); + nonLaravelDeclFinder.set(getPreferences().getBoolean(NON_LARAVEL_DECL_FINDER, false)); + this.bladeComponentsClassFolderList = createModelForBladeComponentFolderList(); + } + + public void storeDirectiveCustomizerPaths() { + String includePath = UIOptionsUtils.encodeToStrings(directiveCustomizerPathList.elements()); + getPreferences().put(DIRECTIVE_CUSTOMIZER_PATH_LIST, includePath); + } + + public void storeViewsPaths() { + String includePath = UIOptionsUtils.encodeToStrings(viewsPathList.elements()); + getPreferences().put(VIEW_PATH_LIST, includePath); + } + + public void storeNonLaravelDeclFinderFlag(boolean status) { + nonLaravelDeclFinder.set(status); + getPreferences().putBoolean(NON_LARAVEL_DECL_FINDER, status); + } + + public void addDirectiveCustomizerPath(String path) { + directiveCustomizerPathList.addElement(path); + } + + public void addViewsPath(String path) { + viewsPathList.addElement(path); + } + + public void removeCustomizerPath(int index) { + directiveCustomizerPathList.remove(index); + } + + public void removeViewsPath(int index) { + viewsPathList.remove(index); + } + + public void setViewsPathList(DefaultListModel list) { + String includePath = UIOptionsUtils.encodeToStrings(list.elements()); + getPreferences().put(VIEW_PATH_LIST, includePath); + } + + public DefaultListModel createModelForDirectiveCusomizerPathList() { + return creatModelFromPreferences(DIRECTIVE_CUSTOMIZER_PATH_LIST); + } + + public DefaultListModel createModelForViewsPathList() { + return creatModelFromPreferences(VIEW_PATH_LIST); + } + + public DefaultListModel getModelForDirectiveCusomizerPathList() { + return directiveCustomizerPathList; + } + + public DefaultListModel getModelViewsPathList() { + return viewsPathList; + } + + public boolean getNonLaravelDeclFinderFlag() { + return nonLaravelDeclFinder.get(); + } + + //blade components + public void addCustomBladeComponentClassFolder(String path) { + bladeComponentsClassFolderList.addElement(path); + } + + public void removeCustomBladeComponentClassFolder(int index) { + bladeComponentsClassFolderList.remove(index); + } + + public DefaultListModel createModelForBladeComponentFolderList() { + return creatModelFromPreferences(BLADE_COMPONENT_CLASS_FOLDER_LIST); + } + + public DefaultListModel getModelForBladeComponentsClassFolderList() { + return bladeComponentsClassFolderList; + } + + public void storeBladeComponentsFolder() { + String includePath = UIOptionsUtils.encodeToStrings(bladeComponentsClassFolderList.elements()); + getPreferences().put(BLADE_COMPONENT_CLASS_FOLDER_LIST, includePath); + } + + private DefaultListModel creatModelFromPreferences(String pathName) { + DefaultListModel model = new DefaultListModel<>(); + String encodedCompilerPathList = getPreferences().get(pathName, null); + String[] paths; + + if (encodedCompilerPathList != null) { + paths = encodedCompilerPathList.split(ESCAPED_VIEW_PATH_SEPARATOR, -1); + } else { + return model; + } + if (paths.length == 0) { + return model; + } + + for (String path : paths) { + model.addElement(path); + } + + return model; + } + + public String[] getCompilerPathList() { + String encodedCompilerPathList = getPreferences().get(DIRECTIVE_CUSTOMIZER_PATH_LIST, null); + String[] paths = new String[]{}; + if (encodedCompilerPathList != null) { + return encodedCompilerPathList.split(ESCAPED_VIEW_PATH_SEPARATOR, -1); + } + return paths; + } + + public String[] getViewsFolderPathList() { + String encodedViewsFolderPathList = getPreferences().get(VIEW_PATH_LIST, null); + String[] paths = new String[]{}; + if (encodedViewsFolderPathList != null) { + return encodedViewsFolderPathList.split(ESCAPED_VIEW_PATH_SEPARATOR, -1); + } + return paths; + } + + public String[] getBladeComponentsClassPathList() { + String encodedBladeComponentFolderPathList = getPreferences().get(BLADE_COMPONENT_CLASS_FOLDER_LIST, null); + String[] paths = new String[]{}; + if (encodedBladeComponentFolderPathList != null) { + return encodedBladeComponentFolderPathList.split(ESCAPED_VIEW_PATH_SEPARATOR, -1); + } + return paths; + } + + public void addPreferenceChangeListener(PreferenceChangeListener preferenceChangeListener) { + getPreferences().addPreferenceChangeListener(preferenceChangeListener); + } + + public void removePreferenceChangeListener(PreferenceChangeListener preferenceChangeListener) { + getPreferences().removePreferenceChangeListener(preferenceChangeListener); + } + +} diff --git a/php/php.blade/src/org/netbeans/modules/php/blade/project/BladeProjectSupport.java b/php/php.blade/src/org/netbeans/modules/php/blade/project/BladeProjectSupport.java new file mode 100644 index 000000000000..cc4ae8be57ad --- /dev/null +++ b/php/php.blade/src/org/netbeans/modules/php/blade/project/BladeProjectSupport.java @@ -0,0 +1,68 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.php.blade.project; + +import org.netbeans.api.project.Project; +import org.netbeans.spi.project.ProjectServiceProvider; +import org.netbeans.spi.project.ui.ProjectOpenedHook; +/** + * + * @author bhaidu + */ +public class BladeProjectSupport extends ProjectOpenedHook { + public static final String APP_PROVIDER_RELATIVE_PATH = "app/Providers/AppServiceProvider.php"; // NOI18N + private final Project project; + + public BladeProjectSupport(Project project) { + assert project != null; + this.project = project; + } + + private static BladeProjectSupport create(Project project) { + BladeProjectSupport support = new BladeProjectSupport(project); + return support; + } + + @ProjectServiceProvider(service = ProjectOpenedHook.class, projectType = "org-netbeans-modules-php-blade-project") // NOI18N + public static BladeProjectSupport forBladeProject(Project project) { + return create(project); + } + + @ProjectServiceProvider(service = ProjectOpenedHook.class, projectType = "org-netbeans-modules-php-project") // NOI18N + public static BladeProjectSupport forPhpProject(Project project) { + return create(project); + } + + @ProjectServiceProvider(service = ProjectOpenedHook.class, projectType = "org-netbeans-modules-web-project") // NOI18N + public static BladeProjectSupport forWebProject(Project project) { + return create(project); + } + + @Override + protected void projectOpened() { + BladeProjectProperties.getInstance(project); + ComponentsSupport.getInstance(project); + } + + @Override + protected void projectClosed() { + BladeProjectProperties.closeProject(project); + } + +} diff --git a/php/php.blade/src/org/netbeans/modules/php/blade/project/Bundle.properties b/php/php.blade/src/org/netbeans/modules/php/blade/project/Bundle.properties new file mode 100644 index 000000000000..0eee8c6fd7de --- /dev/null +++ b/php/php.blade/src/org/netbeans/modules/php/blade/project/Bundle.properties @@ -0,0 +1,21 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +LBL_BladeOptionsName=Blade +CodeCompletionPanel.autoCompletionEscapedEchoDelimitersCheckBox.text=Use &Smart Escaped Echo Delimiters ({!!) Completion +CodeCompletionPanel.autoCompletionEchoDelimiterCheckBox.text=&Use Smart Echo Delimiters ({{) Completion +CodeCompletionPanel.autoCompletionSmartQuotesDelimitersLabel.text=Echo Delimiters Completion: diff --git a/php/php.blade/src/org/netbeans/modules/php/blade/project/ComponentsSupport.java b/php/php.blade/src/org/netbeans/modules/php/blade/project/ComponentsSupport.java new file mode 100644 index 000000000000..0c4256db0490 --- /dev/null +++ b/php/php.blade/src/org/netbeans/modules/php/blade/project/ComponentsSupport.java @@ -0,0 +1,255 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.php.blade.project; + +import java.io.File; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.Future; +import java.util.concurrent.atomic.AtomicBoolean; +import org.netbeans.api.project.Project; +import org.netbeans.modules.php.blade.editor.components.ComponentModel; +import org.netbeans.modules.php.blade.editor.components.annotation.Namespace; +import org.netbeans.modules.php.blade.editor.components.annotation.NamespaceRegister; +import org.netbeans.modules.php.blade.editor.parser.ParsingUtils; +import org.netbeans.modules.php.blade.syntax.StringUtils; +import org.netbeans.modules.php.editor.parser.PHPParseResult; +import org.netbeans.modules.php.editor.parser.astnodes.ASTNode; +import org.netbeans.modules.php.editor.parser.astnodes.ClassDeclaration; +import org.netbeans.modules.php.editor.parser.astnodes.Expression; +import org.netbeans.modules.php.editor.parser.astnodes.FormalParameter; +import org.netbeans.modules.php.editor.parser.astnodes.MethodDeclaration; +import org.netbeans.modules.php.editor.parser.astnodes.visitors.DefaultVisitor; +import org.openide.filesystems.FileObject; +import org.openide.filesystems.FileUtil; +import org.openide.util.Cancellable; +import org.openide.util.RequestProcessor; + +/** + * + * Laravel Project support for blade components + * + * At the first action request for a blade component tag completion or decl + * finder the scan for blade components configuration will be triggered. + * + * The usage of ComponentModels collection is to have information about the file + * and custom attributes + * + * @author bogdan + */ +//list of most common laravel framework directory paths for blade components classes +@NamespaceRegister({ + @Namespace(path = "App\\View\\Components", fromApp = true, relativeFilePath = "app/View/Components"), + @Namespace(path = "App\\Http\\Livewire", fromApp = true, relativeFilePath = "app/Http/Livewire"), + @Namespace(path = "App\\Livewire", fromApp = true, relativeFilePath = "app/Livewire"),//from 10 + @Namespace(path = "Illuminate\\Console\\View\\Components"), + @Namespace(path = "BladeUI\\Icons\\Components", packageName = "blade-ui-kit/blade-icons", relativeFilePath = "blade-ui-kit/blade-icons/src/Components"), + @Namespace(path = "BladeUIKit\\Components", packageName = "blade-ui-kit/blade-ui-kit", relativeFilePath = "blade-ui-kit/blade-ui-kit/src/Components"),}) +public class ComponentsSupport { + + public static final String COMPONENT_TAG_NAME_PREFIX = "x-"; //NOI18N + public static final String COMPONENT_TAG_PREFIX = "<" + COMPONENT_TAG_NAME_PREFIX; //NOI18N + public static final int COMPONENT_TAG_PREFIX_LENGTH = COMPONENT_TAG_PREFIX.length(); + + private static final Map INSTANCES = new HashMap<>(); + private final Map installedComponentNamespace = new HashMap<>(); + + private static final RequestProcessor RP = new RequestProcessor(ComponentsSupport.class); + private static final AtomicBoolean installationScan = new AtomicBoolean(false); + private final Project project; + + private final Map componentClassCollection = new HashMap<>(); + + private ComponentsSupport(Project project) { + this.project = project; + } + + public static ComponentsSupport getInstance(Project project) { + synchronized (INSTANCES) { + if (INSTANCES.containsKey(project)) { + return INSTANCES.get(project); + } + ComponentsSupport instance = new ComponentsSupport(project); + INSTANCES.put(project, instance); + return instance; + } + } + + public void scanForInstalledComponents() { + for (Namespace namespace : getRegisteredNamespaces()) { + FileObject fo = null; + if (namespace.fromApp()) { + //check if folder exists + fo = project.getProjectDirectory().getFileObject(namespace.relativeFilePath()); + } else if (namespace.relativeFilePath() != null && namespace.relativeFilePath().length() > 0) { + fo = project.getProjectDirectory().getFileObject("vendor/" + namespace.relativeFilePath()); // NOI18N + + } + if (fo == null || !fo.isValid()) { + continue; + } + installedComponentNamespace.put(fo, namespace); + RP.submit(new ComponentParsingTask(fo, componentClassCollection)); + } + + installationScan.set(true); + } + + public void scanCustomComponentsFolders() { + BladeProjectProperties bladeProperties = BladeProjectProperties.getInstance(project); + String componentsFolder[] = bladeProperties.getBladeComponentsClassPathList(); + for (String folder : componentsFolder) { + if (folder.length() == 0) { + continue; + } + File folderFile = new File(folder); + if (!folderFile.exists()) { + continue; + } + + FileObject folderObj = FileUtil.toFileObject(folderFile); + RP.submit(new ComponentParsingTask(folderObj, componentClassCollection)); + } + } + + public void scanBladeComponentsClassFolder(FileObject file) { + RP.submit(new ComponentParsingTask(file, componentClassCollection)); + } + + public boolean isScanned() { + return installationScan.get(); + } + + public Map getInstalledComponentNamespace() { + return installedComponentNamespace; + } + + public Namespace[] getRegisteredNamespaces() { + NamespaceRegister namespaceRegister = this.getClass().getAnnotation(NamespaceRegister.class); + return namespaceRegister.value(); + } + + public Map getComponentClassCollection() { + return componentClassCollection; + } + + public ComponentModel findComponentClass(FileObject file) { + return componentClassCollection.get(file); + } + + public static String tag2ClassName(String identifier) { + return identifier.length() > COMPONENT_TAG_PREFIX_LENGTH ? StringUtils.kebabToCamel(identifier.substring(COMPONENT_TAG_PREFIX_LENGTH)) : ""; // NOI18N + } + + private final class ComponentParsingTask implements Runnable, Cancellable { + + private final FileObject root; + private volatile boolean cancelled; + private volatile Future future; + private final Map componentCollection; + + private ComponentParsingTask(FileObject root, Map componentCollection) { + this.root = root; + this.componentCollection = componentCollection; + } + + @Override + public void run() { + //recursive search + Enumeration children = root.getChildren(true); + while (children.hasMoreElements()) { + FileObject file = children.nextElement(); + if (file.isFolder() || !file.getExt().endsWith("php")) { // NOI18N + continue; + } + if (!cancelled) { + ParsingUtils parsingUtils = new ParsingUtils(); + parsingUtils.laterParseFileObject(file); + PHPParseResult result = parsingUtils.getParserResult(); + if (result != null) { + ComponentModel model = new ComponentModel(file); + result.getProgram().accept(new ComponentModelVisitor(model)); + if (model.isValid()) { + componentCollection.putIfAbsent(file, model); + } + } + } + } + } + + @Override + public boolean cancel() { + cancelled = true; + if (future != null) { + future.cancel(true); + } + return true; + } + } + + private class ComponentModelVisitor extends DefaultVisitor { + + private final ComponentModel model; + + public ComponentModelVisitor(ComponentModel model) { + this.model = model; + } + + @Override + public void scan(ASTNode node) { + if (node != null) { + super.scan(node); + } + } + + @Override + public void visit(ClassDeclaration node) { + super.visit(node); + Expression superClass = node.getSuperClass(); + String superClassName = sanitazeClassName(superClass.toString()); + model.checkClassValidity(superClassName); + } + + @Override + public void visit(MethodDeclaration node) { + if (node.getFunction() == null) { + return; + } + String functionName = node.getFunction().getFunctionName().getName(); + if (functionName.equals("__construct")) { // NOI18N + List formalParameters = node.getFunction().getFormalParameters(); + for (FormalParameter parameter : formalParameters) { + model.addConstructorProperty(parameter); + } + } + } + + @Override + public void visit(FormalParameter node) { + //short constructor + model.addConstructorProperty(node); + } + + private String sanitazeClassName(String className) { + return className.replace("\\", ""); // NOI18N + } + } +} diff --git a/php/php.blade/src/org/netbeans/modules/php/blade/project/ProjectUtils.java b/php/php.blade/src/org/netbeans/modules/php/blade/project/ProjectUtils.java new file mode 100644 index 000000000000..3f23c7382a35 --- /dev/null +++ b/php/php.blade/src/org/netbeans/modules/php/blade/project/ProjectUtils.java @@ -0,0 +1,57 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.php.blade.project; + +import org.netbeans.api.annotations.common.CheckForNull; +import org.netbeans.api.project.Project; +import org.netbeans.spi.project.ui.support.ProjectConvertors; +import org.openide.filesystems.FileObject; + +/** + * + * @author bogdan + */ +public final class ProjectUtils { + + private ProjectUtils() { + + } + + @CheckForNull + public static Project getMainOwner(FileObject file) { + Project project = ProjectConvertors.getNonConvertorOwner(file); + + if (project == null) { + return null; + } + + return project; + } + + @CheckForNull + public static FileObject getProjectDirectory(FileObject file) { + Project project = getMainOwner(file); + + if (project == null) { + return null; + } + + return project.getProjectDirectory(); + } +} diff --git a/php/php.blade/src/org/netbeans/modules/php/blade/resources/Bundle.properties b/php/php.blade/src/org/netbeans/modules/php/blade/resources/Bundle.properties new file mode 100644 index 000000000000..b41a44aa11a8 --- /dev/null +++ b/php/php.blade/src/org/netbeans/modules/php/blade/resources/Bundle.properties @@ -0,0 +1,35 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +OpenIDE-Module-Display-Category=PHP +OpenIDE-Module-Long-Description=\ + Support for Blade template syntax for PHP.\n\ + Still experimental.\n\ +

@author Haidu Bogdan

\n\n

Latest updates

\n\n[2.5.0.0] - fix IDE freeze on '?\ + :' short if expr, fix namespace static method completion
\n[2.4.9.9] - limited go to file + completion for vite assets, decl finder fixes + refactor for php code
\n[2.4.9.8] - fix comment highlight issue in directive php expression, improvements on php namespace completion
\n[2.4.9.7] - fix livewire component formatting, display namespace path on class method completion
\n[2.4.9.6] - detect namespace path on directives php expression\n[2.4.9.5] - fix directive completion before double quote html, add attr directive to completion list
\n[2.4.9.4] - fix issue 57 formatting with optgroup
\n[2.4.9.3] - configurable declaration finder for view paths
\n[2.4.9.1] - fix block tag completion cursor preview
\n[2.4.8.9] - auto tag completion is true by default
\n[2.4.8.8] - fix issue 61, endcan tag breaks syntax highlighting
\n[2.4.8.7] - cleaning code, refactor
\n[2.4.8.7] - cleaning code, refactor
\n[2.4.8] - change License to Apache, blade tag autocomplete
\n[2.4.7] - debug lag with completion
\n[2.4.6] - use custom BladeCompletionItem, add @lang
\n[2.4.5] - use custom BladeCompletionItem
\n[2.4.3.2] - disable unused parser caching
\n[2.3.4.3] - [code] brace matcher is using annotation
\n[2.3.4.2] - fix navigator regression hafter disabling the ParserListener
\n[2.3.4.1] - fix php parser error for empty `@php` blocks
\n[2.3.9.3] - fix comment parser error regression
\n[2.3.7.2 - 2.3.8.3] - refactor directive completion
\n[2.3.6.2] - refactored parser for optimisation +OpenIDE-Module-Name=Php Blade +OpenIDE-Module-Short-Description=Support for Blade template engine for PHP. + +UnclosedIfError="Unclosed @if" + +blade_directive=Blade directive +blade_comment=Blade comment +blade_echo_delimiters=Blade echo tags +blade_php=Php +mod-undefined=Custom Directive effect +mod-declaration=Custom Directive +blade_comp_attribute=Blade Component Attribute + diff --git a/php/php.blade/src/org/netbeans/modules/php/blade/resources/FlatLafDark-FlatLafDark.xml b/php/php.blade/src/org/netbeans/modules/php/blade/resources/FlatLafDark-FlatLafDark.xml new file mode 100644 index 000000000000..6d7a0ded7c64 --- /dev/null +++ b/php/php.blade/src/org/netbeans/modules/php/blade/resources/FlatLafDark-FlatLafDark.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/php/php.blade/src/org/netbeans/modules/php/blade/resources/FontAndColors.xml b/php/php.blade/src/org/netbeans/modules/php/blade/resources/FontAndColors.xml new file mode 100644 index 000000000000..e8b874ec735a --- /dev/null +++ b/php/php.blade/src/org/netbeans/modules/php/blade/resources/FontAndColors.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/php/php.blade/src/org/netbeans/modules/php/blade/resources/NewBladeFileDescription.html b/php/php.blade/src/org/netbeans/modules/php/blade/resources/NewBladeFileDescription.html new file mode 100644 index 000000000000..64aba5479af0 --- /dev/null +++ b/php/php.blade/src/org/netbeans/modules/php/blade/resources/NewBladeFileDescription.html @@ -0,0 +1,29 @@ + + + + + Blade wizard + + + + +
Create new file
+ + diff --git a/php/php.blade/src/org/netbeans/modules/php/blade/resources/emptyBladeFile.blade.php b/php/php.blade/src/org/netbeans/modules/php/blade/resources/emptyBladeFile.blade.php new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/php/php.blade/src/org/netbeans/modules/php/blade/resources/formattingExample.blade.php b/php/php.blade/src/org/netbeans/modules/php/blade/resources/formattingExample.blade.php new file mode 100644 index 000000000000..6bf35049eac4 --- /dev/null +++ b/php/php.blade/src/org/netbeans/modules/php/blade/resources/formattingExample.blade.php @@ -0,0 +1,16 @@ +@extends('my.layout') + +
+ {{-- BLADE COMMENT --}} + @if ($test) + {{-- REGULAR ECHO --}} + {{ $x }} + + {{-- RAW ECHO --}} + {!! $x !!} + @endif + + @verbatim +
{{escaped}}
+ @endverbatim +
\ No newline at end of file diff --git a/php/php.blade/src/org/netbeans/modules/php/blade/resources/highlightBlade.blade.php b/php/php.blade/src/org/netbeans/modules/php/blade/resources/highlightBlade.blade.php new file mode 100644 index 000000000000..85d6e4b01ca1 --- /dev/null +++ b/php/php.blade/src/org/netbeans/modules/php/blade/resources/highlightBlade.blade.php @@ -0,0 +1,22 @@ +@extends('my.layout') + +
+ {{-- BLADE COMMENT --}} + @if ($test) + {{-- REGULAR ECHO --}} + {{ $x }} + + {{-- RAW ECHO --}} + {!! $x !!} + @endif + + + + + + @custom('local') + + @verbatim +
{{escaped}}
+ @endverbatim +
diff --git a/php/php.blade/src/org/netbeans/modules/php/blade/resources/icon.png b/php/php.blade/src/org/netbeans/modules/php/blade/resources/icon.png new file mode 100644 index 000000000000..4e24f58cf55c Binary files /dev/null and b/php/php.blade/src/org/netbeans/modules/php/blade/resources/icon.png differ diff --git a/php/php.blade/src/org/netbeans/modules/php/blade/resources/icons/at.png b/php/php.blade/src/org/netbeans/modules/php/blade/resources/icons/at.png new file mode 100644 index 000000000000..4f74305ff196 Binary files /dev/null and b/php/php.blade/src/org/netbeans/modules/php/blade/resources/icons/at.png differ diff --git a/php/php.blade/src/org/netbeans/modules/php/blade/resources/icons/blade_file.png b/php/php.blade/src/org/netbeans/modules/php/blade/resources/icons/blade_file.png new file mode 100644 index 000000000000..4e24f58cf55c Binary files /dev/null and b/php/php.blade/src/org/netbeans/modules/php/blade/resources/icons/blade_file.png differ diff --git a/php/php.blade/src/org/netbeans/modules/php/blade/resources/icons/layout.png b/php/php.blade/src/org/netbeans/modules/php/blade/resources/icons/layout.png new file mode 100644 index 000000000000..a184f9796c8a Binary files /dev/null and b/php/php.blade/src/org/netbeans/modules/php/blade/resources/icons/layout.png differ diff --git a/php/php.blade/src/org/netbeans/modules/php/blade/resources/layer.xml b/php/php.blade/src/org/netbeans/modules/php/blade/resources/layer.xml new file mode 100644 index 000000000000..58ee6ff9c307 --- /dev/null +++ b/php/php.blade/src/org/netbeans/modules/php/blade/resources/layer.xml @@ -0,0 +1,112 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/php/php.blade/src/org/netbeans/modules/php/blade/resources/mime-resolver.xml b/php/php.blade/src/org/netbeans/modules/php/blade/resources/mime-resolver.xml new file mode 100644 index 000000000000..65f2c52db668 --- /dev/null +++ b/php/php.blade/src/org/netbeans/modules/php/blade/resources/mime-resolver.xml @@ -0,0 +1,30 @@ + + + + + + + + + \ No newline at end of file diff --git a/php/php.blade/src/org/netbeans/modules/php/blade/resources/preferences.xml b/php/php.blade/src/org/netbeans/modules/php/blade/resources/preferences.xml new file mode 100644 index 000000000000..5eb201132c4d --- /dev/null +++ b/php/php.blade/src/org/netbeans/modules/php/blade/resources/preferences.xml @@ -0,0 +1,29 @@ + + + + + + true + true + true + 4 + \ No newline at end of file diff --git a/php/php.blade/src/org/netbeans/modules/php/blade/syntax/BladeDirectivesUtils.java b/php/php.blade/src/org/netbeans/modules/php/blade/syntax/BladeDirectivesUtils.java new file mode 100644 index 000000000000..19911519846a --- /dev/null +++ b/php/php.blade/src/org/netbeans/modules/php/blade/syntax/BladeDirectivesUtils.java @@ -0,0 +1,130 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.php.blade.syntax; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; +import org.netbeans.api.annotations.common.CheckForNull; +import org.netbeans.modules.php.blade.syntax.annotation.Directive; + +/** + * + * + * @author bhaidu + */ +public final class BladeDirectivesUtils { + + public static final String AT = "@"; // NOI18N + public static final String END_DIRECTIVE_PREFIX = "@end"; // NOI18N + public static final String DIRECTIVE_SECTION = "@section"; // NOI18N + public static final String DIRECTIVE_HAS_SECTION = "@hasSection"; // NOI18N + public static final String DIRECTIVE_SECTION_MISSING = "@sectionMissing"; // NOI18N + public static final String DIRECTIVE_ENDSECTION = "@endsection"; // NOI18N + public static final String DIRECTIVE_SHOW = "@show"; // NOI18N + public static final String DIRECTIVE_STOP = "@stop"; // NOI18N + public static final String DIRECTIVE_APPEND = "@append"; // NOI18N + public static final String DIRECTIVE_OVERWRITE = "@overwrite"; // NOI18N + public static final String DIRECTIVE_IF = "@if"; // NOI18N + public static final String DIRECTIVE_ELSEIF = "@elseif"; // NOI18N + public static final String DIRECTIVE_ELSE = "@else"; // NOI18N + public static final String DIRECTIVE_ENDIF = "@endif"; // NOI18N + public static final String DIRECTIVE_FOREACH = "@foreach"; // NOI18N + public static final String DIRECTIVE_INCLUDE = "@include"; // NOI18N + public static final String DIRECTIVE_EXTENDS = "@extends"; // NOI18N + public static final String DIRECTIVE_SESSION = "@session"; // NOI18N + public static final String DIRECTIVE_CAN = "@can"; // NOI18N + + public static String[] blockDirectiveEndings(String directive) { + + if (directive.equals(DIRECTIVE_SECTION)) { + return new String[]{DIRECTIVE_ENDSECTION, DIRECTIVE_SHOW, DIRECTIVE_STOP, DIRECTIVE_APPEND, DIRECTIVE_OVERWRITE}; + } + + DirectivesList listClass = new DirectivesList(); + for (Directive directiveEl : listClass.getDirectives()) { + + if (!directiveEl.name().equals(directive)) { + continue; + } + if (directiveEl.endtag().isEmpty()) { + return null; + } + return new String[]{directiveEl.endtag()}; + } + return null; + } + + @CheckForNull + public static String[] blockDirectiveOpenings(String directive) { + switch (directive) { + case DIRECTIVE_ENDIF -> { + return new String[]{DIRECTIVE_IF, DIRECTIVE_HAS_SECTION, DIRECTIVE_SECTION_MISSING}; + } + case DIRECTIVE_ELSEIF -> { + return new String[]{DIRECTIVE_IF, DIRECTIVE_ELSEIF}; + } + case DIRECTIVE_ELSE -> { + return new String[]{DIRECTIVE_IF, DIRECTIVE_ELSEIF, DIRECTIVE_CAN}; + } + case DIRECTIVE_ENDSECTION, DIRECTIVE_APPEND, DIRECTIVE_STOP, DIRECTIVE_SHOW, DIRECTIVE_OVERWRITE -> { + return new String[]{DIRECTIVE_SECTION}; + } + } + DirectivesList listClass = new DirectivesList(); + for (Directive directiveEl : listClass.getDirectives()) { + if (directiveEl.endtag().isEmpty()) { + continue; + } + if (directiveEl.endtag().equals(directive)) { + return new String[]{directiveEl.name()}; + } + } + + return null; + } + + public static Set blockDirectiveOpeningsSet(String[] endings) { + Set result = new HashSet<>(); + + for (String endDirective : endings) { + String[] startDirectives = blockDirectiveOpenings(endDirective); + + if (startDirectives != null) { + result.addAll(Arrays.asList(startDirectives)); + } + } + + return result; + } + + public static Set blockDirectiveEndingsSet(String[] openings) { + Set result = new HashSet<>(); + + for (String startDirective : openings) { + String[] endDirectives = blockDirectiveEndings(startDirective); + + if (endDirectives != null) { + result.addAll((Arrays.asList(endDirectives))); + } + } + + return result; + } +} diff --git a/php/php.blade/src/org/netbeans/modules/php/blade/syntax/BladeTags.java b/php/php.blade/src/org/netbeans/modules/php/blade/syntax/BladeTags.java new file mode 100644 index 000000000000..757ffa5a32ad --- /dev/null +++ b/php/php.blade/src/org/netbeans/modules/php/blade/syntax/BladeTags.java @@ -0,0 +1,39 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.php.blade.syntax; + +import org.netbeans.modules.php.blade.syntax.annotation.Tag; +import org.netbeans.modules.php.blade.syntax.annotation.TagRegister; + +/** + * + * @author bhaidu + */ +@TagRegister({ + @Tag(openTag = "{{", closeTag = "}}", description = "regular echo", position=0), + @Tag(openTag = "{!!", closeTag = "!!}", description = "raw echo", position=1), + @Tag(openTag = "{{--", closeTag = "--}}", description = "comment", position=2), +}) +public class BladeTags { + + public Tag[] getTags() { + TagRegister tagRegister = this.getClass().getAnnotation(TagRegister.class); + return tagRegister.value(); + } +} diff --git a/php/php.blade/src/org/netbeans/modules/php/blade/syntax/BladeTagsUtils.java b/php/php.blade/src/org/netbeans/modules/php/blade/syntax/BladeTagsUtils.java new file mode 100644 index 000000000000..981f4a98503a --- /dev/null +++ b/php/php.blade/src/org/netbeans/modules/php/blade/syntax/BladeTagsUtils.java @@ -0,0 +1,44 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.php.blade.syntax; + +/** + * + * @author bhaidu + */ +public final class BladeTagsUtils { + + public static final String CONTENT_TAG_OPEN = "{{"; //NOI18N + public static final String CONTENT_TAG_CLOSE = "}}"; //NOI18N + + public static final String RAW_TAG_OPEN = "{!!"; //NOI18N + public static final String RAW_TAG_CLOSE = "!!}"; //NOI18N + + private BladeTagsUtils(){ + + } + + public static String[] outputCloseTags() { + return new String[]{CONTENT_TAG_CLOSE, RAW_TAG_CLOSE}; + } + + public static String[] outputStartTags() { + return new String[]{CONTENT_TAG_OPEN, RAW_TAG_OPEN}; + } +} \ No newline at end of file diff --git a/php/php.blade/src/org/netbeans/modules/php/blade/syntax/BladeVariables.java b/php/php.blade/src/org/netbeans/modules/php/blade/syntax/BladeVariables.java new file mode 100644 index 000000000000..331cd94dacbe --- /dev/null +++ b/php/php.blade/src/org/netbeans/modules/php/blade/syntax/BladeVariables.java @@ -0,0 +1,32 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.php.blade.syntax; + +/** + * + * @author bogdan + */ +public final class BladeVariables { + + public static final String LOOP_VAR = "$loop"; // NOI18N + + private BladeVariables(){ + + } +} diff --git a/php/php.blade/src/org/netbeans/modules/php/blade/syntax/DirectivesList.java b/php/php.blade/src/org/netbeans/modules/php/blade/syntax/DirectivesList.java new file mode 100644 index 000000000000..1dc3d09ffaa1 --- /dev/null +++ b/php/php.blade/src/org/netbeans/modules/php/blade/syntax/DirectivesList.java @@ -0,0 +1,126 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.php.blade.syntax; + +import org.netbeans.modules.php.blade.syntax.annotation.Directive; +import org.netbeans.modules.php.blade.syntax.annotation.DirectiveRegister; + +/** + * + * the since values are taken from + * https://github.com/onecentlin/laravel-blade-snippets-vscode + * + * + * @author bhaidu + */ +@DirectiveRegister({ + //conditionals + @Directive(name = "@if", params = true, endtag = "@endif"), + @Directive(name = "@elseif", params = true), + @Directive(name = "@else"), + @Directive(name = "@endif"), + @Directive(name = "@empty", since = "5.4"), + @Directive(name = "@empty", params = true, endtag = "@endempty", since = "5.4"), + @Directive(name = "@isset", params = true, endtag = "@endisset"), + @Directive(name = "@unless", params = true, endtag = "@endunless"), + //loops + @Directive(name = "@for", params = true, endtag = "@endfor"), + @Directive(name = "@foreach", params = true, endtag = "@endforeach"), + @Directive(name = "@forelse", params = true, endtag = "@endforelse"), + @Directive(name = "@break"), + @Directive(name = "@continue"), + @Directive(name = "@endfor"), + @Directive(name = "@endforeach"), + @Directive(name = "@endforelse"), + //layout + @Directive(name = "@extends", params = true), + @Directive(name = "@section", params = true, endtag = "@endsection", endTags = {"@endsection", "@show", "@stop", "@append"}), + @Directive(name = "@endsection"), + @Directive(name = "@stop"), + @Directive(name = "@append"), + @Directive(name = "@once"), + @Directive(name = "@endonce"), + @Directive(name = "@overwrite"), + @Directive(name = "@yield", params = true), + @Directive(name = "@hasSection", params = true, endtag = "@endif"), + @Directive(name = "@sectionMissing", params = true, endtag = "@endif"), + @Directive(name = "@include", params = true), + @Directive(name = "@includeFirst", params = true), + @Directive(name = "@includeIf", params = true), + @Directive(name = "@includeWhen", params = true), + @Directive(name = "@includeUnless", params = true), + @Directive(name = "@each", params = true), + @Directive(name = "@verbatim", endtag = "@endverbatim"), + //statement + @Directive(name = "@switch", params = true, endtag = "@endswitch"), + @Directive(name = "@case", params = true), + @Directive(name = "@default"), + //stack + @Directive(name = "@stack", params = true), + @Directive(name = "@push", params = true, endtag = "@endpush"), + @Directive(name = "@endpush"), + @Directive(name = "@prepend", params = true, endtag = "@endprepend"), + @Directive(name = "@endprepend"), + @Directive(name = "@pushIf", params = true, endtag = "@endPushIf"), + @Directive(name = "@endPushIf"), + // + @Directive(name = "@fragment", params = true, endtag = "@endfragment"), + @Directive(name = "@endfragment"), + //form + @Directive(name = "@csrf"), + @Directive(name = "@method", params = true), + @Directive(name = "@error", params = true, endtag = "@enderror"), + //env + @Directive(name = "@env", params = true, endtag = "@endenv"), + //auth + @Directive(name = "@auth", params = true, endtag = "@endauth"), + @Directive(name = "@guest", params = true, endtag = "@endguest"), + @Directive(name = "@can", params = true, endtag = "@endcan", since = "5.1"), + @Directive(name = "@canany", params = true, endtag = "@endcanany", since = "5.8"), + @Directive(name = "@cannot", params = true, endtag = "@endcannot", since = "5.3"), + //php + @Directive(name = "@php", endtag = "@endphp"), + @Directive(name = "@use", params = true), + @Directive(name = "@inject", params = true), + //utils + @Directive(name = "@lang", params = true), + @Directive(name = "@session", params = true, endtag = "@endsession", since = "10"), + @Directive(name = "@dump", params = true), + @Directive(name = "@dd", params = true), + @Directive(name = "@json", params = true), + @Directive(name = "@vite", params = true, since = "11"), + @Directive(name = "@when", params = true, since = "11"), + @Directive(name = "@bool", params = true, since = "11"), + //styles + @Directive(name = "@props", params = true, since = "7.4"), + @Directive(name = "@class", params = true, since = "8"), + @Directive(name = "@style", params = true, since = "9"), + @Directive(name = "@checked", params = true, since = "9"), + @Directive(name = "@disabled", params = true, since = "9"), + @Directive(name = "@readony", params = true, since = "9"), + @Directive(name = "@required", params = true, since = "9"), + } +) +public class DirectivesList { + + public Directive[] getDirectives() { + DirectiveRegister directiveRegister = this.getClass().getAnnotation(DirectiveRegister.class); + return directiveRegister.value(); + } +} diff --git a/php/php.blade/src/org/netbeans/modules/php/blade/syntax/StringUtils.java b/php/php.blade/src/org/netbeans/modules/php/blade/syntax/StringUtils.java new file mode 100644 index 000000000000..1e6252e3d6da --- /dev/null +++ b/php/php.blade/src/org/netbeans/modules/php/blade/syntax/StringUtils.java @@ -0,0 +1,80 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.php.blade.syntax; + +/** + * + * @author bhaidu + */ +public final class StringUtils { + + public static final String DOT = "."; //NOI18N + public static final String ESCAPED_DOT = "\\."; //NOI18N + public static final String FORWARD_SLASH = "/"; //NOI18N + + private StringUtils() { + + } + + public static boolean isUpperCase(String s) { + for (char c : s.toCharArray()) { + if (!Character.isLetter(c)) { + continue; + } + if (!Character.isUpperCase(c)) { + return false; + } + } + + return true; + } + + public static String capitalize(String str) { + if (str == null || str.length() <= 1) { + return str; + } + return str.substring(0, 1).toUpperCase() + str.substring(1); + } + + public static String toKebabCase(String str) { + return str.replaceAll("([A-Z])", "-$1").toLowerCase().substring(1); //NOI18N + } + + public static String kebabToCamel(String str) { + str = str.toLowerCase(); + String[] words = str.split("-"); //NOI18N + String camelCase = words[0]; + for (int i = 1; i < words.length; i++) { + camelCase += words[i].substring(0, 1).toUpperCase() + words[i].substring(1); + } + return capitalize(camelCase); + } + + public static boolean isWhitespace(String text) { + return text.replaceAll(" ", "").isEmpty(); //NOI18N + } + + public static String replaceLinesAndTabs(String input) { + String escapedString = input; + escapedString = escapedString.replaceAll("\n", "\\\\n"); // NOI18N + escapedString = escapedString.replaceAll("\r", "\\\\r"); // NOI18N + escapedString = escapedString.replaceAll("\t", "\\\\t"); // NOI18N + return escapedString; + } +} diff --git a/php/php.blade/src/org/netbeans/modules/php/blade/syntax/ViewPathUtils.java b/php/php.blade/src/org/netbeans/modules/php/blade/syntax/ViewPathUtils.java new file mode 100644 index 000000000000..9342677c8a98 --- /dev/null +++ b/php/php.blade/src/org/netbeans/modules/php/blade/syntax/ViewPathUtils.java @@ -0,0 +1,50 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.php.blade.syntax; + +/** + * + * @author bogdan + */ +public final class ViewPathUtils { + + private ViewPathUtils() { + + } + + public static int getViewPathSeparatorOffset(String pathName, int offset) { + int lastDotPos; + + if (pathName.endsWith(StringUtils.DOT)) { + lastDotPos = pathName.length(); + } else { + lastDotPos = pathName.lastIndexOf(StringUtils.DOT); + } + int pathOffset; + + if (lastDotPos > 0) { + int dotFix = pathName.endsWith(StringUtils.DOT) ? 0 : 1; + pathOffset = offset - pathName.length() + lastDotPos + dotFix; + } else { + pathOffset = offset - pathName.length(); + } + + return pathOffset; + } +} diff --git a/php/php.blade/src/org/netbeans/modules/php/blade/syntax/annotation/Directive.java b/php/php.blade/src/org/netbeans/modules/php/blade/syntax/annotation/Directive.java new file mode 100644 index 000000000000..3af6eaafe76e --- /dev/null +++ b/php/php.blade/src/org/netbeans/modules/php/blade/syntax/annotation/Directive.java @@ -0,0 +1,33 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.php.blade.syntax.annotation; + +/** + * + * @author bhaidu + */ +public @interface Directive { + String name(); + boolean params() default false; + String endtag() default ""; //NOI18N + String[] endTags() default {}; + String description() default ""; //NOI18N + String category() default ""; //NOI18N + String since() default ""; //NOI18N +} diff --git a/php/php.blade/src/org/netbeans/modules/php/blade/syntax/annotation/DirectiveRegister.java b/php/php.blade/src/org/netbeans/modules/php/blade/syntax/annotation/DirectiveRegister.java new file mode 100644 index 000000000000..7d3cbabd1a24 --- /dev/null +++ b/php/php.blade/src/org/netbeans/modules/php/blade/syntax/annotation/DirectiveRegister.java @@ -0,0 +1,34 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.php.blade.syntax.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * + * @author bhaidu + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(value = {ElementType.TYPE, ElementType.FIELD, ElementType.METHOD, ElementType.PACKAGE}) +public @interface DirectiveRegister { + public Directive[] value(); +} diff --git a/php/php.blade/src/org/netbeans/modules/php/blade/syntax/annotation/PhpKeyword.java b/php/php.blade/src/org/netbeans/modules/php/blade/syntax/annotation/PhpKeyword.java new file mode 100644 index 000000000000..6d9424111a6b --- /dev/null +++ b/php/php.blade/src/org/netbeans/modules/php/blade/syntax/annotation/PhpKeyword.java @@ -0,0 +1,31 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.php.blade.syntax.annotation; + +/** + * + * @author bhaidu + */ +public @interface PhpKeyword { + String name(); + boolean params() default false; + boolean parenExpr() default false; + String description() default ""; + String category() default ""; +} diff --git a/php/php.blade/src/org/netbeans/modules/php/blade/syntax/annotation/PhpKeywordRegister.java b/php/php.blade/src/org/netbeans/modules/php/blade/syntax/annotation/PhpKeywordRegister.java new file mode 100644 index 000000000000..9e59d4dc88f7 --- /dev/null +++ b/php/php.blade/src/org/netbeans/modules/php/blade/syntax/annotation/PhpKeywordRegister.java @@ -0,0 +1,34 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.php.blade.syntax.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * + * @author bhaidu + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(value = {ElementType.TYPE, ElementType.FIELD, ElementType.METHOD, ElementType.PACKAGE}) +public @interface PhpKeywordRegister { + public PhpKeyword[] value(); +} diff --git a/php/php.blade/src/org/netbeans/modules/php/blade/syntax/annotation/Tag.java b/php/php.blade/src/org/netbeans/modules/php/blade/syntax/annotation/Tag.java new file mode 100644 index 000000000000..360ce94122d9 --- /dev/null +++ b/php/php.blade/src/org/netbeans/modules/php/blade/syntax/annotation/Tag.java @@ -0,0 +1,30 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.php.blade.syntax.annotation; + +/** + * + * @author bhaidu + */ +public @interface Tag { + String openTag(); + String closeTag() default ""; //NOI18N + String description() default ""; //NOI18N + int position() default 0; +} diff --git a/php/php.blade/src/org/netbeans/modules/php/blade/syntax/annotation/TagRegister.java b/php/php.blade/src/org/netbeans/modules/php/blade/syntax/annotation/TagRegister.java new file mode 100644 index 000000000000..cfd488ab2f7f --- /dev/null +++ b/php/php.blade/src/org/netbeans/modules/php/blade/syntax/annotation/TagRegister.java @@ -0,0 +1,34 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.php.blade.syntax.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * + * @author bhaidu + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(value = {ElementType.TYPE, ElementType.FIELD, ElementType.METHOD, ElementType.PACKAGE}) +public @interface TagRegister { + public Tag[] value(); +} diff --git a/php/php.blade/src/org/netbeans/modules/php/blade/syntax/antlr4/formatter/BladeAntlrFormatterLexer.g4 b/php/php.blade/src/org/netbeans/modules/php/blade/syntax/antlr4/formatter/BladeAntlrFormatterLexer.g4 new file mode 100644 index 000000000000..2818e0b5b9f2 --- /dev/null +++ b/php/php.blade/src/org/netbeans/modules/php/blade/syntax/antlr4/formatter/BladeAntlrFormatterLexer.g4 @@ -0,0 +1,213 @@ + /* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +lexer grammar BladeAntlrFormatterLexer; + +@header{ + /* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.php.blade.syntax.antlr4.formatter; +} + +options { + superClass = LexerAdaptor; + caseInsensitive = true; +} + +//we will hide html in the end +tokens {HTML, PHP_CODE, PARAM_COMMA} + + channels { COMMENT } + + fragment DirectiveLabel + : [a-z\u0080-\ufffe][a-z0-9_\u0080-\ufffe]*; + +fragment Identifier + : [a-z\u0080-\ufffe][a-z0-9_\u0080-\ufffe]*; + +fragment CompomentIdentifier + : [a-z\u0080-\ufffe][a-z0-9_.:\u0080-\ufffe]*; + +fragment DirectiveArgLookup + : (' ')* {this._input.LA(1) == '('}?; + +fragment DOUBLE_QUOTED_STRING_FRAGMENT + : '"' ([\\"] | . )*? '"'; + +fragment SINGLE_QUOTED_STRING_FRAGMENT + : '\'' (~('\'' | '\\') | '\\' . )* '\''; + +fragment BlockDirectiveName + : 'auth' | 'guest' + | 'if' | 'can' ('any' | 'not')? | 'for' ('each' | 'else')? + | 'while' | 'hasSection' | 'sectionMissing' | 'fragment' | 'verbatim' + | 'isset' | 'unless' | 'empty' + | 'session' + | 'env' | 'once' | 'error' + | 'push' ('if' | 'once')? | 'prepend' | 'switch'; + +PHP_INLINE : '' | ''; +// +D_ESCAPES + : ( + '{{{' + | '@@' '@'? + | '@{' '{'? + | '@media' [ ]* + | '@charset' + | '@import' + | '@namespace' + | '@document' + | '@font-face' + | '@page' + | '@supports' + | '@layer' + | '@tailwind' + | '@apply' + | '@-webkit-keyframes' + | '@keyframes' + )->type(HTML); + +D_BLOCK_DIRECTIVE_START : ('@' BlockDirectiveName DirectiveArgLookup)->pushMode(DIRECTIVE_ARG); +D_BLOCK_DIRECTIVE_START_NO_ARG : '@' ('auth' | 'production')->type(D_BLOCK_DIRECTIVE_START); +D_BLOCK_DIRECTIVE_END : '@end' BlockDirectiveName | '@endphp'; + +D_SECTION : ('@section' DirectiveArgLookup)->pushMode(DIRECTIVE_ARG_WITH_PARAM); +D_ENDSECTION : '@endsection' | '@show' | '@append' | '@stop'; +D_BLOCK_ALIGNED_DIRECTIVE : '@else' | '@elseif' | '@empty'; +NON_PARAM_DIRECTIVE : '@continue' | '@break'; + +D_INLINE_DIRECTIVE : '@' DirectiveLabel DirectiveArgLookup | '@csrf'; + +STRING : DOUBLE_QUOTED_STRING_FRAGMENT | SINGLE_QUOTED_STRING_FRAGMENT; + +CONTENT_TAG_OPEN : '{{' ->pushMode(INSIDE_REGULAR_ECHO); +RAW_TAG_OPEN : '{!!' ->pushMode(INSIDE_RAW_ECHO); + +SG_QUOTE : '\''; +DB_QUOTE : '"'; + +HTML_CLOSE_TAG : ('<' (' ')* '/' (' ')* [a-z\u0080-\ufffe][a-z0-9_.\u0080-\ufffe]* (' ')* '>') +| ('') +; +HTML_COMMENT: ''; +HTML_START_BLOCK_TAG : '<' ('div' + | 'section' | 'main' | 'article' + | 'html' | 'title' | 'head' | 'style' | 'script' | 'footer' + | 'pre' | 'code' | 'blockquote' + | 'dt' | 'dl' | 'video' + | 'template' + | 'span' | 'strong' | 'em' | 'small' | 'sub' | 'sup' + | 'figure' | 'canvas' | 'svg' | 'use' | 'path' | 'polygon' | 'picture' + | 'header' | 'h' [1-9] | 'nav' + | 'dialog' + | 'summary' | 'details' | 'slot' + | 'label' | 'select' | 'optgroup' | 'option' | 'fieldset' | 'textarea' | 'button' | 'form' | 'search' + | 'ul' | 'ol' | 'li' + | 'table' | 'tr' | 'td' | 'th' | 'tbody' | 'thead' | 'tfoot' | 'caption' | + | 'time' | + | 'var' | 'q' | 'p' | 'a' | 'b' | 'i') {this._input.LA(1) == '>' || this._input.LA(1) == '@' || this._input.LA(1) == ' ' || this._input.LA(1) == '\n'}?; + + +HTML_SELF_CLOSE_TAG : '<' ('img' | 'input' | 'br' | 'hr' | 'link' | 'meta'); + +COMPONENT_TAG : ''; +GT_SYMBOL : '>'; + +D_PHP : '@php' {this._input.LA(1) == ' ' || this._input.LA(1) == '\n'}?->pushMode(BLADE_INLINE_PHP); + +AT : '@' ->skip; + +WS : ((' ') | [\t])+; +NL : [\r\n]+; + + + +OTHER : . ->skip; + +mode DIRECTIVE_ARG; + +D_ARG_LPAREN : '(' {this.consumeDirectiveArgLParen();}; +D_ARG_RPAREN : ')' {this.consumeDirectiveArgRParen();}; + +D_ARG_NL : [\r\n]->type(NL); + +PHP_EXPR : . ->skip; + +EXIT_EOF : EOF->popMode; + +mode DIRECTIVE_ARG_WITH_PARAM; + +D_ARG_PARAM_LPAREN : '(' {this.consumeDirectiveArgLParen();}; +D_ARG_PARAM_RPAREN : ')' {this.consumeDirectiveArgRParen();}; + +BL_SQ_LPAREN : '[' {this.squareParenBalance++;}->skip; +BL_SQ_RPAREN : ']' {this.squareParenBalance--;}->skip; + +BL_CURLY_LPAREN : '{' {this.curlyParenBalance++;}->skip; +BL_CURLY_RPAREN : '}' {this.curlyParenBalance--;}->skip; + +D_ARG_COMMA_EL : ',' {this.consumeBladeParamComma();}; +D_ARG_PARAM_NL : [\r\n]->type(NL); + +BL_PHP_EXPR : . ->skip; + +BL_EXIT_EOF : EOF->popMode; + +mode BLADE_INLINE_PHP; + +D_ENDPHP : '@endphp'->popMode; + +PHP_CODE_GREEDY : ~[@]+->type(PHP_CODE); + +PHP_CODE_COMPLETION : . ->type(PHP_CODE); + +// {{ }} +mode INSIDE_REGULAR_ECHO; + +CONTENT_TAG_CLOSE : ('}}')->popMode; +CONTENT_OTHER : . ->skip; +EXIT_REGULAR_ECHO_EOF : EOF->popMode; + +// {!! !!} +mode INSIDE_RAW_ECHO; + +RAW_TAG_CLOSE : ('!!}')->popMode; +RAW_CONTENT_OTHER : . ->skip; +EXIT_RAW_ECHO_EOF : EOF->popMode; \ No newline at end of file diff --git a/php/php.blade/src/org/netbeans/modules/php/blade/syntax/antlr4/formatter/BladeAntlrFormatterParser.g4 b/php/php.blade/src/org/netbeans/modules/php/blade/syntax/antlr4/formatter/BladeAntlrFormatterParser.g4 new file mode 100644 index 000000000000..e03478370132 --- /dev/null +++ b/php/php.blade/src/org/netbeans/modules/php/blade/syntax/antlr4/formatter/BladeAntlrFormatterParser.g4 @@ -0,0 +1,88 @@ +parser grammar BladeAntlrFormatterParser; + +@header{ + /* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.php.blade.syntax.antlr4.formatter; +} + +options { tokenVocab = BladeAntlrFormatterLexer; } + +file : statement* EOF; + +statement: + html_indent + | block_start + | block_end + | html_tag + | self_closed_tag + | section_block + | inline_identable_element + | block_aligned_directive + | static_element + | nl_with_space_after + | blade_echo + | block_end + | html_close_tag + | (SG_QUOTE | DB_QUOTE) + | INLINE_GT_SYMBOL + | GT_SYMBOL + | (NL | WS) + ; + +inline_tag_statement : + IDENTIFIER EQ IDENTIFIER + | block_start + | block_end + | blade_echo + | D_INLINE_DIRECTIVE + | NON_PARAM_DIRECTIVE + | D_BLOCK_ALIGNED_DIRECTIVE + | IDENTIFIER EQ STRING + | IDENTIFIER + | STRING + | EQ + | WS + | NL + ; + +html_close_tag : HTML_CLOSE_TAG; + +html_indent : (HTML_START_BLOCK_TAG | COMPONENT_TAG) inline_tag_statement* GT_SYMBOL NL WS*; +html_tag : (HTML_START_BLOCK_TAG | COMPONENT_TAG) inline_tag_statement* GT_SYMBOL; +self_closed_tag : HTML_SELF_CLOSE_TAG | ((HTML_START_BLOCK_TAG | COMPONENT_TAG) inline_tag_statement* INLINE_GT_SYMBOL); +//block_start : ws_before=nl_with_space_before? block_directive_name D_ARG_LPAREN D_ARG_RPAREN ; +block_start : D_BLOCK_DIRECTIVE_START D_ARG_LPAREN D_ARG_RPAREN ws_after=nl_with_space_after*; +block_end : D_BLOCK_DIRECTIVE_END; +block_aligned_directive : D_BLOCK_ALIGNED_DIRECTIVE; +inline_identable_element : D_INLINE_DIRECTIVE | NON_PARAM_DIRECTIVE | section_inline + | blade_echo | D_PHP PHP_CODE+ D_ENDPHP + ; + +section_inline : D_SECTION D_ARG_LPAREN PARAM_COMMA D_ARG_RPAREN; +section_block : D_SECTION D_ARG_LPAREN D_ARG_RPAREN ws_after=nl_with_space_after* (statement)+ D_ENDSECTION; +nl_with_space_after : NL+ WS*; +nl_with_space : NL WS*; + +static_element : HTML_COMMENT + | HTML + | OTHER; + +blade_echo : (CONTENT_TAG_OPEN CONTENT_TAG_CLOSE) + | (RAW_TAG_OPEN RAW_TAG_CLOSE); \ No newline at end of file diff --git a/php/php.blade/src/org/netbeans/modules/php/blade/syntax/antlr4/formatter/LexerAdaptor.java b/php/php.blade/src/org/netbeans/modules/php/blade/syntax/antlr4/formatter/LexerAdaptor.java new file mode 100644 index 000000000000..36923de2fb18 --- /dev/null +++ b/php/php.blade/src/org/netbeans/modules/php/blade/syntax/antlr4/formatter/LexerAdaptor.java @@ -0,0 +1,95 @@ + /* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.php.blade.syntax.antlr4.formatter; + +import org.antlr.v4.runtime.CharStream; +import org.antlr.v4.runtime.Lexer; +import org.antlr.v4.runtime.Token; + +/** + * + * @author bogdan + */ +public abstract class LexerAdaptor extends Lexer { + + private int _currentRuleType = Token.INVALID_TYPE; + public int roundParenBalance = 0; + public int squareParenBalance = 0; + public int curlyParenBalance = 0; + public int exitIfModePosition = 0; + + public LexerAdaptor(CharStream input) { + super(input); + } + + public int getCurrentRuleType() { + return _currentRuleType; + } + + public void setCurrentRuleType(int ruleType) { + this._currentRuleType = ruleType; + } + + @Override + public Token emit() { + return super.emit(); + } + + @Override + public void reset() { + setCurrentRuleType(Token.INVALID_TYPE); + super.reset(); + } + + public void consumeDirectiveArgLParen() { + if (this.roundParenBalance == 0) { + this.setType(BladeAntlrFormatterLexer.D_ARG_LPAREN); + } else { + this.skip(); + } + this.roundParenBalance++; + } + + public void consumeDirectiveArgRParen() { + //we start from 0 balance + this.roundParenBalance--; + System.out.println("balance " + this.roundParenBalance); + if (this.roundParenBalance <= 0) { + this.setType(BladeAntlrFormatterLexer.D_ARG_RPAREN); + this.roundParenBalance = 0; + this.mode(DEFAULT_MODE); + } else { + this.skip(); + } + } + + public void consumeBladeParamComma() { + if (this.hasNoBladeParamOpenBracket()) { + this.setType(BladeAntlrFormatterLexer.PARAM_COMMA); + } else { + this.skip(); + } + } + + public boolean hasNoBladeParamOpenBracket() { + return this.roundParenBalance == 1 + && this.squareParenBalance == 0 + && this.curlyParenBalance == 0; + } +} diff --git a/php/php.blade/src/org/netbeans/modules/php/blade/syntax/antlr4/html_components/BladeHtmlAntlrLexer.g4 b/php/php.blade/src/org/netbeans/modules/php/blade/syntax/antlr4/html_components/BladeHtmlAntlrLexer.g4 new file mode 100644 index 000000000000..79074df1a12e --- /dev/null +++ b/php/php.blade/src/org/netbeans/modules/php/blade/syntax/antlr4/html_components/BladeHtmlAntlrLexer.g4 @@ -0,0 +1,87 @@ + /* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +lexer grammar BladeHtmlAntlrLexer; + +@header{ + /* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + package org.netbeans.modules.php.blade.syntax.antlr4.html_components; +} + +@lexer::members { + boolean tagOpened = false; + boolean insideTag = false; + int contentTagBalance = 0; + int rawTagBalance = 0; +} + +options { + caseInsensitive = true; +} + +fragment Identifier + : [a-z\u0080-\ufffe][a-z0-9-_\u0080-\ufffe]*; + + +HTML_COMPONENT_OPEN_TAG : '' {insideTag = false;}; + +BLADE_COMMENT_START : '{{--' ->pushMode(INSIDE_BLADE_COMMENT), skip; + +BLADE_TAG_ESCAPE : '@' ('{')+->skip; +CONTENT_TAG_OPEN : '{{' {contentTagBalance++;}->skip; +CONTENT_TAG_CLOSE : '}}' {contentTagBalance--;}->skip; + +RAW_TAG_OPEN : '{!!' {rawTagBalance++;}; +RAW_TAG_CLOSE : '!!}' {rawTagBalance--;}; + +WS : ((' ')+ | [\r\n]+)->skip; + +TAG_PART : '!' | '!!'; + +OTHER : . ->skip; + +//============================================== + +mode INSIDE_BLADE_COMMENT; + +BLADE_COMMENT_END : '--}}'->popMode, skip; + +BLADE_COMMENT_MORE : . ->skip; + +BLADE_COMMENT_EOF : EOF->popMode, skip; \ No newline at end of file diff --git a/php/php.blade/src/org/netbeans/modules/php/blade/syntax/antlr4/html_components/BladeHtmlAntlrParser.g4 b/php/php.blade/src/org/netbeans/modules/php/blade/syntax/antlr4/html_components/BladeHtmlAntlrParser.g4 new file mode 100644 index 000000000000..2c26a00c6ce1 --- /dev/null +++ b/php/php.blade/src/org/netbeans/modules/php/blade/syntax/antlr4/html_components/BladeHtmlAntlrParser.g4 @@ -0,0 +1,48 @@ + /* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +parser grammar BladeHtmlAntlrParser; + +@header{ + /* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + package org.netbeans.modules.php.blade.syntax.antlr4.html_components; +} + +options { tokenVocab = BladeHtmlAntlrLexer; } + +root : element* EOF; + +element : + HTML_COMPONENT_OPEN_TAG; \ No newline at end of file diff --git a/php/php.blade/src/org/netbeans/modules/php/blade/syntax/antlr4/html_components/BladeHtmlAntlrUtils.java b/php/php.blade/src/org/netbeans/modules/php/blade/syntax/antlr4/html_components/BladeHtmlAntlrUtils.java new file mode 100644 index 000000000000..949d2e89f9d3 --- /dev/null +++ b/php/php.blade/src/org/netbeans/modules/php/blade/syntax/antlr4/html_components/BladeHtmlAntlrUtils.java @@ -0,0 +1,70 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.php.blade.syntax.antlr4.html_components; + +import java.util.Set; +import javax.swing.text.BadLocationException; +import javax.swing.text.Document; +import org.antlr.v4.runtime.CharStreams; +import org.antlr.v4.runtime.Token; +import org.netbeans.modules.php.blade.syntax.antlr4.utils.BaseBladeAntlrUtils; +import org.netbeans.spi.lexer.antlr4.AntlrTokenSequence; + +/** + * + * @author bogdan + */ +public final class BladeHtmlAntlrUtils extends BaseBladeAntlrUtils { + + public static AntlrTokenSequence getTokens(Document doc) { + + try { + String text = doc.getText(0, doc.getLength()); + return new AntlrTokenSequence(new BladeHtmlAntlrLexer(CharStreams.fromString(text))); + } catch (BadLocationException ex) { + + } + return null; + } + + + public static Token findBackwardWithStop( + AntlrTokenSequence tokens, int tokenMatch, Set stopTokens) { + if (tokens == null || tokens.isEmpty()) { + return null; + } + + while (tokens.hasPrevious()) { + Token pt = tokens.previous().get(); + if (pt == null) { + continue; + } + + if (pt.getType() == tokenMatch) { + return pt; + } + + if (stopTokens.contains(pt.getType())) { + return null; + } + } + + return null; + } +} diff --git a/php/php.blade/src/org/netbeans/modules/php/blade/syntax/antlr4/php/BladePhpAntlrLexer.g4 b/php/php.blade/src/org/netbeans/modules/php/blade/syntax/antlr4/php/BladePhpAntlrLexer.g4 new file mode 100644 index 000000000000..6ce49ebe2bec --- /dev/null +++ b/php/php.blade/src/org/netbeans/modules/php/blade/syntax/antlr4/php/BladePhpAntlrLexer.g4 @@ -0,0 +1,108 @@ + /* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +lexer grammar BladePhpAntlrLexer; +import BladePhpCommonLexer; + +@header{ + /* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + package org.netbeans.modules.php.blade.syntax.antlr4.php; +} + +@lexer::members { + int rparenBalance = 0; + int sqparenBalance = 0; + int curlyparenBalance = 0; +} + +options { + caseInsensitive = true; +} + +LINE_COMMENT : LineComment->skip; + +ARRAY : 'array'; +AS : 'as'; +ECHO : 'echo'; +IF : 'if'; +ELSEIF : 'elseif' | 'else if'; +ELSE : 'else'; +NEW : 'new'; +CLASS : 'class'; +FUNCTION : 'function'; +LANG_CONSTRUCT : 'empty' | 'isset'; +MATCH : 'match'; +FOREACH : 'foreach'; + +COMMA : ',' ; + +LPAREN : '('; +RPAREN : ')'; + +LSQUAREBRACKET: '['; +RSQUAREBRACKET: ']'; + +LCURLYBRACE: '{'; +RCURLYBRACE: '}'; + +IDENTIFIER : Identifier; + +PHP_VARIABLE : PhpVariable; + +DOLLAR : '$'; + +NAMESPACE_SEPARATOR : '\\'; +DOUBLE_COLON : '::'; +ARROW : '=>'; +OBJECT_OPERATOR : '->'; + +SEMI_COLON : ';'; + +COMPARISON_OPERATOR : ('==' | '!=' | '>' | '<') '='?; + +LOGICAL_UNION_OPERATOR : '&&' | '||'; + +STRING_LITERAL : StringLiteral; + + +STYLE_COMMENT : '/*' .*? '*/' [\n\r]*->skip; + +WS : ((' ')+ | [\r\n]+)->skip; + +//testing purpose +PHP_DIRECTIVE : ('@php' | '@endphp')->skip; + +OTHER : . ->skip; + diff --git a/php/php.blade/src/org/netbeans/modules/php/blade/syntax/antlr4/php/BladePhpAntlrParser.g4 b/php/php.blade/src/org/netbeans/modules/php/blade/syntax/antlr4/php/BladePhpAntlrParser.g4 new file mode 100644 index 000000000000..a4617a63fcfd --- /dev/null +++ b/php/php.blade/src/org/netbeans/modules/php/blade/syntax/antlr4/php/BladePhpAntlrParser.g4 @@ -0,0 +1,211 @@ + /* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +parser grammar BladePhpAntlrParser; + +@header{ + /* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + package org.netbeans.modules.php.blade.syntax.antlr4.php; +} + +options { + superClass = ParserAdaptor; + tokenVocab = BladePhpAntlrLexer; + } + +expression : exprStatement* EOF; + +exprStatement : + //empty statement + ';' + | '{' exprStatement* '}' + | '(' exprStatement* ')' + | COMPARISON_OPERATOR exprStatement + | LOGICAL_UNION_OPERATOR functionalExpr+ + | 'foreach' '(' foreachArguments ')' + | functionalExpr + | ifStatement + | foreachDirectiveStatement + | output + | misc +; + +logicalStatement: + '(' functionalExpr* ')' + | functionalExpr (LOGICAL_UNION_OPERATOR functionalExpr)+ + | functionalExpr (COMPARISON_OPERATOR functionalExpr)+ + | functionalExpr +; + +ifStatement: + //long expr + 'if' '(' logicalStatement+ ')' '{' exprStatement* '}' + (ELSEIF '(' logicalStatement+ ')' '{' exprStatement* '}' )* + ('else' '{' exprStatement* '}')? + ; + +inputExpr: + varExpr + ; + +functionalExpr: + matchStatement + | classExpression (classMember)* + | functionExpr + | LANG_CONSTRUCT '(' functionalExpr ')' + | inputExpr + ; + +classExpression: + classInstanceStatement + | staticMethodAccess + | staticFieldAccess + | staticClassReference + | staticAccess + | aliasDirectAccess + | directMethodAccess + ; + +foreachDirectiveStatement: + {this.bladeParserContext.equals(ParserContext.FOREACH)}? foreachArguments + ; + +foreachArguments: + (array | main_array = PHP_VARIABLE) 'as' array_item=PHP_VARIABLE + | (array | main_array = PHP_VARIABLE) 'as' array_key=PHP_VARIABLE '=>' array_item=PHP_VARIABLE + | functionExpr 'as' functionExpr ('=>' functionExpr)? +; + +classInstanceStatement: + 'new' namespace? className=IDENTIFIER arguments? + ; + +matchStatement: + 'match' '(' functionalExpr ')' '{' + (functionalExpr+ (',' functionalExpr+)* '=>' functionalExpr+)* + (',' functionalExpr+ (',' functionalExpr+)* '=>' functionalExpr+)* + ','? + '}' +; +staticClassReference : + namespace? IDENTIFIER '::' CLASS +; + +staticMethodAccess : + namespace? className=IDENTIFIER '::' method=IDENTIFIER arguments +; + +staticFieldAccess : + namespace? className=IDENTIFIER '::' (const=IDENTIFIER | propertyAlias=PHP_VARIABLE | 'class') + | classAlias=PHP_VARIABLE '::' const=IDENTIFIER + | classAlias=PHP_VARIABLE '::' propertyAlias=PHP_VARIABLE +; + +staticAccess : + //should throw an error? + namespace? className=IDENTIFIER '::' +; + +aliasDirectAccess: + PHP_VARIABLE classMember + ; + +classMember: + directMethodAccess + | '->' IDENTIFIER +; + +directMethodAccess : + '->' IDENTIFIER arguments +; + +directAccess : + IDENTIFIER arguments '->' IDENTIFIER arguments +; + +functionExpr : + IDENTIFIER arguments +; + +arguments : + '(' argument (',' argument )* ')' + | '(' ')' + ; + +namespace : + '\\'? (IDENTIFIER '\\')+ + | '\\' +; + +argument: + functionalExpr + | IDENTIFIER + ; + +array: + PHP_VARIABLE array_key_item+ + | array_key_item +; + +array_key_item: + '[' array_key_item* ']' + | 'array' '(' array_key_item* ')' + | '[' (array_child '=>' array_key_item)+ ']' + | 'array' '(' (array_child '=>' array_key_item)+ ')' + | '[' array_child (',' array_child)* ','? ']' + | 'array' '(' array_child (',' array_child)* ','? ')' +; + +array_child: + functionalExpr '=>' array_key_item + | functionalExpr '=>' functionalExpr + | functionalExpr + ; + +varExpr: + array + | '$'? PHP_VARIABLE + | STRING_LITERAL +; + +misc: + 'new' PHP_VARIABLE arguments? + | 'new' namespace //incomplete namespcace + | namespace className=IDENTIFIER +; + +output: + 'echo' functionalExpr +; \ No newline at end of file diff --git a/php/php.blade/src/org/netbeans/modules/php/blade/syntax/antlr4/php/BladePhpAntlrUtils.java b/php/php.blade/src/org/netbeans/modules/php/blade/syntax/antlr4/php/BladePhpAntlrUtils.java new file mode 100644 index 000000000000..6464a11c7331 --- /dev/null +++ b/php/php.blade/src/org/netbeans/modules/php/blade/syntax/antlr4/php/BladePhpAntlrUtils.java @@ -0,0 +1,65 @@ + /* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.php.blade.syntax.antlr4.php; + +import org.netbeans.modules.php.blade.syntax.antlr4.v10.*; +import java.util.List; +import java.util.Set; +import javax.swing.text.BadLocationException; +import javax.swing.text.Document; +import org.antlr.v4.runtime.CharStream; +import org.antlr.v4.runtime.CharStreams; +import org.antlr.v4.runtime.CommonTokenStream; +import org.antlr.v4.runtime.Token; +import static org.netbeans.modules.php.blade.syntax.antlr4.v10.BladeAntlrParser.*; +import org.netbeans.spi.lexer.antlr4.AntlrTokenSequence; + +/** + * + * @author bogdan + */ +public class BladePhpAntlrUtils { + + public static AntlrTokenSequence lexerStringScan(String text) { + CharStream cs = CharStreams.fromString(text); + BladePhpAntlrLexer lexer = new BladePhpAntlrLexer(cs); + AntlrTokenSequence tokens = new AntlrTokenSequence(lexer); + return tokens; + } + + public static Token getToken(String text, int offset) { + AntlrTokenSequence tokens = lexerStringScan(text); + if (offset > text.length()){ + return null; + } + + if (tokens.isEmpty()){ + return null; + } + + tokens.seekTo(offset); + + if (!tokens.hasNext()){ + return null; + } + + Token token = tokens.next().get(); + return token; + } +} diff --git a/php/php.blade/src/org/netbeans/modules/php/blade/syntax/antlr4/php/BladePhpCommonLexer.g4 b/php/php.blade/src/org/netbeans/modules/php/blade/syntax/antlr4/php/BladePhpCommonLexer.g4 new file mode 100644 index 000000000000..b7d3ed5c17af --- /dev/null +++ b/php/php.blade/src/org/netbeans/modules/php/blade/syntax/antlr4/php/BladePhpCommonLexer.g4 @@ -0,0 +1,88 @@ + /* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +lexer grammar BladePhpCommonLexer; + +tokens { + HTML, + BLADE_COMMENT +} + +fragment Identifier + : [a-z_\u0080-\ufffe][a-z0-9_\u0080-\ufffe]*; + +fragment HtmlIdentifier + : [a-z_\u0080-\ufffe][a-z0-9_\u0080-\ufffe-]*; + +fragment ESC_DOUBLE_QUOTED_STRING + : [\\"]; + +fragment DOUBLE_QUOTED_STRING_FRAGMENT + : '"' (ESC_DOUBLE_QUOTED_STRING | . )*? '"'; + +fragment SINGLE_QUOTED_STRING_FRAGMENT + : '\'' (~('\'' | '\\') | '\\' . )* '\''; + +fragment StringLiteral : DOUBLE_QUOTED_STRING_FRAGMENT | SINGLE_QUOTED_STRING_FRAGMENT; + +fragment LineComment + : '//' ~ [\r\n]* + ; + +fragment PhpVariable + : '$' Identifier; + + +fragment Digit + : ('0'..'9'); + +BLADE_COMMENT_START : '{{--' ->pushMode(INSIDE_BLADE_COMMENT), skip; + +EMAIL_SUBSTRING : ('@' Identifier '.')->skip; + +VERSION_WITH_AT: '@' (Digit '.'?)+->skip; + +//escapes +D_ESCAPES + : ( + '{{{' + | '@@' '@'? + | '@{' '{'? + | '@media' [ ]+ ('screen' [ ]+ 'and'?)? + | ( '@charset' | '@import' | '@namespace' | '@document' | '@font-face' + | '@page' | '@layer' | '@supports' | '@tailwind' | '@apply' | '@-webkit-keyframes' + | '@keyframes' | '@counter-style' | '@font-feature-values' | '@property' + | '@scope' | '@starting-style' | '@supports' | '@view-transition' + | '@container' | '@color-profile' | '@styleset' | '@font-palette-values' | '@media' + ) [ ]* + )->type(HTML); + +mode INSIDE_BLADE_COMMENT; + +BLADE_COMMENT_END : '--}}'->popMode, skip; + +//hack to merge all php inputs into one token +BLADE_COMMENT_PEEK : . { + this._input.LA(1) == '-' && + this._input.LA(2) == '-' && + this._input.LA(3) == '}' && + this._input.LA(4) == '}' + }? ->skip; +BLADE_COMMENT_MORE : . ->skip; + +BLADE_COMMENT_EOF : EOF->popMode, skip; \ No newline at end of file diff --git a/php/php.blade/src/org/netbeans/modules/php/blade/syntax/antlr4/php/ParserAdaptor.java b/php/php.blade/src/org/netbeans/modules/php/blade/syntax/antlr4/php/ParserAdaptor.java new file mode 100644 index 000000000000..c2fe4af5844e --- /dev/null +++ b/php/php.blade/src/org/netbeans/modules/php/blade/syntax/antlr4/php/ParserAdaptor.java @@ -0,0 +1,45 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.php.blade.syntax.antlr4.php; + +import org.antlr.v4.runtime.Parser; +import org.antlr.v4.runtime.TokenStream; + +/** + * + * @author bogdan + */ +public abstract class ParserAdaptor extends Parser { + public static enum ParserContext { + FOREACH, + OUTPUT, + STANDARD + } + + protected ParserContext bladeParserContext = ParserContext.STANDARD; + + public ParserAdaptor(TokenStream input) { + super(input); + } + + public void setBladeParserContext(ParserContext context){ + this.bladeParserContext = context; + } + +} diff --git a/php/php.blade/src/org/netbeans/modules/php/blade/syntax/antlr4/utils/BaseBladeAntlrUtils.java b/php/php.blade/src/org/netbeans/modules/php/blade/syntax/antlr4/utils/BaseBladeAntlrUtils.java new file mode 100644 index 000000000000..c99ae5df8122 --- /dev/null +++ b/php/php.blade/src/org/netbeans/modules/php/blade/syntax/antlr4/utils/BaseBladeAntlrUtils.java @@ -0,0 +1,131 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.php.blade.syntax.antlr4.utils; + +import java.util.Set; +import org.antlr.v4.runtime.Token; +import org.netbeans.spi.lexer.antlr4.AntlrTokenSequence; + +/** + * + * @author bogdan + */ +public class BaseBladeAntlrUtils { + + public static Token getToken(AntlrTokenSequence tokens, int offset) { + if (tokens == null || tokens.isEmpty()) { + return null; + } + + tokens.seekTo(offset); + + if (!tokens.hasNext()) { + return null; + } + + Token token = tokens.next().get(); + + //need to move back + if (token != null && tokens.hasPrevious() && token.getStartIndex() > offset && token.getStopIndex() > offset) { + token = tokens.previous().get(); + } + + return token; + } + + public static Token findForward(AntlrTokenSequence tokens, int startOffset, + Set stopTokensText, Set openTokensText) { + if (tokens == null || tokens.isEmpty()) { + return null; + } + tokens.seekTo(startOffset); + + return findForward(tokens, stopTokensText, openTokensText); + } + + private static Token findForward(AntlrTokenSequence tokens, + Set stopTokenText, Set openTokensText) { + + int openTokenBalance = 0; + + while (tokens.hasNext()) { + Token nt = tokens.next().get(); + if (nt == null) { + continue; + } + + String tokenText = nt.getText().trim(); + + if (openTokensText.contains(tokenText)) { + openTokenBalance++; + continue; + } + if (stopTokenText.contains(tokenText)) { + if (openTokenBalance > 0) { + openTokenBalance--; + } else { + return nt; + } + } + } + return null; + } + + public static Token findBackward(AntlrTokenSequence tokens, int offset, + Set stopTokenText, Set balanceTokensText) { + + if (tokens == null || tokens.isEmpty()) { + return null; + } + + tokens.seekTo(offset); + + return findBackward(tokens, stopTokenText, balanceTokensText); + } + + private static Token findBackward(AntlrTokenSequence tokens, + Set stopTokenText, Set balanceTokensText) { + + int openTokenBalance = 0; + + while (tokens.hasPrevious()) { + Token pt = tokens.previous().get(); + if (pt == null) { + continue; + } + + String tokenText = pt.getText().trim(); + + if (balanceTokensText.contains(tokenText)) { + openTokenBalance++; + continue; + } + if (stopTokenText.contains(tokenText)) { + if (openTokenBalance > 0) { + openTokenBalance--; + } else { + return pt; + } + } + } + + return null; + } + +} \ No newline at end of file diff --git a/php/php.blade/src/org/netbeans/modules/php/blade/syntax/antlr4/utils/BladeAntlrLexerUtils.java b/php/php.blade/src/org/netbeans/modules/php/blade/syntax/antlr4/utils/BladeAntlrLexerUtils.java new file mode 100644 index 000000000000..434b33ad1e12 --- /dev/null +++ b/php/php.blade/src/org/netbeans/modules/php/blade/syntax/antlr4/utils/BladeAntlrLexerUtils.java @@ -0,0 +1,82 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.php.blade.syntax.antlr4.utils; + +import org.netbeans.modules.php.blade.syntax.antlr4.utils.*; +import javax.swing.text.BadLocationException; +import javax.swing.text.Document; +import org.antlr.v4.runtime.CharStream; +import org.antlr.v4.runtime.CharStreams; +import org.antlr.v4.runtime.Token; +import org.netbeans.modules.php.blade.syntax.antlr4.v10.BladeAntlrLexer; +import static org.netbeans.modules.php.blade.syntax.antlr4.v10.BladeAntlrLexer.*; +import org.netbeans.spi.lexer.antlr4.AntlrTokenSequence; + +/** + * + * @author bogdan + */ +public final class BladeAntlrLexerUtils extends BaseBladeAntlrUtils { + + public static AntlrTokenSequence getTokens(Document doc) { + + try { + String text = doc.getText(0, doc.getLength()); + return new AntlrTokenSequence(new BladeAntlrLexer(CharStreams.fromString(text))); + } catch (BadLocationException ex) { + + } + return null; + } + + public static AntlrTokenSequence lexerStringScan(String text) { + CharStream cs = CharStreams.fromString(text); + BladeAntlrLexer lexer = new BladeAntlrLexer(cs); + AntlrTokenSequence tokens = new AntlrTokenSequence(lexer); + return tokens; + } + + public static Token getToken(String text, int offset) { + AntlrTokenSequence tokens = lexerStringScan(text); + if (offset > text.length()) { + return null; + } + tokens.seekTo(offset); + + if (!tokens.hasNext()) { + return null; + } + Token token = tokens.next().get(); + return token; + } + + public static int getTagPairTokenType(int tokenType) { + return switch (tokenType) { + case BLADE_CONTENT_OPEN_TAG -> BLADE_CONTENT_CLOSE_TAG; + case BLADE_CONTENT_CLOSE_TAG -> BLADE_CONTENT_OPEN_TAG; + case BLADE_RAW_OPEN_TAG -> BLADE_RAW_CLOSE_TAG; + case BLADE_RAW_CLOSE_TAG -> BLADE_RAW_OPEN_TAG; + default -> -1; + }; + } + + public static boolean isUndefinedDirective(int token){ + return token == BladeAntlrLexer.D_CUSTOM; + } +} diff --git a/php/php.blade/src/org/netbeans/modules/php/blade/syntax/antlr4/v10/BladeAntlrColoringLexer.g4 b/php/php.blade/src/org/netbeans/modules/php/blade/syntax/antlr4/v10/BladeAntlrColoringLexer.g4 new file mode 100644 index 000000000000..3e29f53ac7ce --- /dev/null +++ b/php/php.blade/src/org/netbeans/modules/php/blade/syntax/antlr4/v10/BladeAntlrColoringLexer.g4 @@ -0,0 +1,241 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +lexer grammar BladeAntlrColoringLexer; +import BladeColoringCommonLexer; + +@header{ + /* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + package org.netbeans.modules.php.blade.syntax.antlr4.v10; +} + +options { + superClass = ColoringLexerAdaptor; + caseInsensitive = true; + } + +channels {PHP_CODE} + +tokens { + DIRECTIVE, + BLADE_PAREN, + PHP_EXPRESSION, + BLADE_PHP_ECHO_EXPR, + BLADE_PHP_INLINE, + RAW_TAG, + CONTENT_TAG, + HTML, + HTML_TAG, + COMPONENT_ATTR, + ERROR +} + +fragment DirectivesWithEndTag : 'for' ('each' | 'else')? | 'if' | 'while' + | 'section' | 'session' | 'once' | 'push' | 'PushOnce' + | 'switch' | 'unless' | 'can' ('any' | 'not')? | 'env' + | 'auth' | 'guest' | 'error' | 'empty' | 'isset' + //11.x + | 'fragment'; + +fragment Include : '@include' ('If' | 'When' | 'First' | 'Unless')?; + +fragment ComponentTagIdentifier + : [a-z_\u0080-\ufffe][a-z0-9.:\u0080-\ufffe-]*; + +//==================================================== +//TOKENS: + +PHP_INLINE : '' | '' | EOF); + +D_GENERIC_BLOCK_DIRECTIVES : ('@' DirectivesWithEndTag | '@sectionMissing' | '@hasSection') (' ')* {this._input.LA(1) == '('}? ->pushMode(INSIDE_PHP_EXPRESSION),type(DIRECTIVE); + +D_SIMPLE_BLOCK_DIRECTIVES : '@' 'end'? ('empty' | 'production' | 'once') ->type(DIRECTIVE); + +D_GENERIC_INLINE_DIRECTIVES : ('@elseif' | Include | '@extends' | '@each' | '@yield' | '@props' | '@method' | '@elsecan' ('any' | 'not')? + | '@class' | '@style' | '@aware' | '@break' | '@continue' | '@selected' | '@disabled' + | '@readonly' | '@required' | '@when' | '@bool') (' ')* {this._input.LA(1) == '('}? ->pushMode(INSIDE_PHP_EXPRESSION),type(DIRECTIVE); + +D_GENERIC_INLINE_MIXED_DIRECTIVES : ('@break' | '@continue' | '@auth' | '@guest')->type(DIRECTIVE); + +D_GENERIC_END_TAGS : ('@stop' | '@show' | '@overwrite' | '@viteReactRefresh' | '@end' DirectivesWithEndTag)->type(DIRECTIVE); + +//verbatim has special blade escape logic +D_VERBATIM : '@verbatim' ->pushMode(VERBATIM_MODE), type(DIRECTIVE); +D_ENDVERBATIM : '@endverbatim'->type(DIRECTIVE); + +D_MISC : ('@dd' | '@dump' | '@js' | '@json' | '@inject' | '@when' | '@bool') (' ')* {this._input.LA(1) == '('}? ->pushMode(INSIDE_PHP_EXPRESSION),type(DIRECTIVE); + +D_SIMPLE : ('@else' 'guest'? | '@csrf' | '@default' | '@append' | '@parent')->type(DIRECTIVE); + +//php emebeddings +D_PHP_SHORT : '@php' (' ')? {this._input.LA(1) == '('}? ->type(D_PHP),pushMode(INSIDE_PHP_EXPRESSION); +D_PHP : '@php'->pushMode(BLADE_INLINE_PHP); + +//allow php expression highlight for custom directives which start with 'end' also +D_END_ARG : ('@end' NameString) (' ')* {this._input.LA(1) == '('}?->pushMode(INSIDE_PHP_EXPRESSION),type(DIRECTIVE); +D_END : ('@end' NameString)->type(DIRECTIVE); + + +D_ASSET_BUNDLER : '@vite' (' ')* {this._input.LA(1) == '('}? ->pushMode(INSIDE_PHP_EXPRESSION),type(DIRECTIVE); + +//known plugins +D_LIVEWIRE : ('@livewireStyles' | '@bukStyles' | '@livewireScripts' | '@bukScripts' | '@click' ('.away')? '=')->type(DIRECTIVE); + +D_SPATIE_ARG : ('@' ( ('unless' | 'has' ('any')? )? 'role') | 'haspermission') {this._input.LA(1) == '('}? ->pushMode(INSIDE_PHP_EXPRESSION),type(DIRECTIVE); + +D_SPATIE : ('@end' ('unless' | 'has' ('any')?)? 'role' | 'haspermission')->type(DIRECTIVE); + +D_CSS_AT_RULE : ('@supports' | '@container' | '@scope' | '@media') (' ')* {this._input.LA(1) == '('}? ->type(HTML); +//we will decide that a custom directive has expression to avoid email matching +D_CUSTOM : ('@' NameString (' ')* {this._input.LA(1) == '('}? ) ->pushMode(INSIDE_PHP_EXPRESSION); + +D_UNKNOWN : '@' NameString->pushMode(ADIACENT_DIRECTIVE_TOKENS); + +//hack to trigger completion handler which is stopped due to embedding context like HTML, PHP +D_AT : '@' (' ' | '>' | [\n\r])?; + +//display +CONTENT_TAG_OPEN : '{{' ->pushMode(INSIDE_REGULAR_ECHO),type(CONTENT_TAG); +RAW_TAG_OPEN : '{!!' ->pushMode(INSIDE_RAW_ECHO),type(RAW_TAG); + +JS_COMMENT : LineComment->type(HTML); +CSS_COMMENT : ('/*' | '*/')->type(HTML); + +HTML_X : ('type(HTML),pushMode(INSIDE_HTML_COMPONENT_TAG); + +CLOSE_TAG : ('' [\n\r ]*)+->type(HTML); + +HTML_WS : ((' ')+ | [\r\n]+)->type(HTML); + +INCOMPLETE_BLADE_TAG : ('{!' | '{{-') ->type(HTML); + +GENERAL_IDENTIFIER : '$'? NameString->type(HTML); + +OTHER : . ->type(HTML); + +//========================================================= +//========================================================= +//MODES +// {{ }} +mode INSIDE_REGULAR_ECHO; + +CONTENT_TAG_CLOSE : ('}}')->popMode,type(CONTENT_TAG); + +GREEDY_REGULAR_ECHO_EXPR : ~[ {}]+ {this.consumeEscapedEchoToken();}; + +ESCAPED_ECHO_EXPR : . [ ]* {this.consumeEscapedEchoToken();}; +EXIT_ECHO_EOF : EOF->type(HTML),popMode; + +//========================================================= +// {!! !!} +mode INSIDE_RAW_ECHO; + +RAW_TAG_CLOSE : ('!!}')->popMode, type(RAW_TAG); + +RAW_ECHO_EXPR : ~[ !{}]+ {this.consumeNotEscapedEchoToken();}; +RAW_ECHO_EXPR_MORE : . [ ]* {this.consumeNotEscapedEchoToken();}; +EXIT_RAW_ECHO_EOF : EOF->type(HTML),popMode; + +//========================================================= +// @directive (?) +mode INSIDE_PHP_EXPRESSION; + +OPEN_PAREN : '(' {this.rParenBalance == 0}? {this.rParenBalance++;}->type(BLADE_PAREN); +OPEN_E_PAREN : '(' {this.rParenBalance++;}->type(PHP_EXPRESSION); +CLOSE_PAREN : ')' {this.rParenBalance <= 1}? {this.rParenBalance = 0;}->type(BLADE_PAREN),mode(DEFAULT_MODE); +CLOSE_E_PAREN : ')' {this.rParenBalance--;}->type(PHP_EXPRESSION); + +PHP_EXPRESSION_COMMENT : ('/*' .*? '*/')->type(PHP_EXPRESSION); + +PHP_EXPRESSION_GREEDY : ~[()]+ ->type(PHP_EXPRESSION); + +PHP_EXPRESSION_MORE : . ->type(PHP_EXPRESSION); + +EXIT_EOF : EOF->type(ERROR),mode(DEFAULT_MODE); + +//========================================================= +// @php +mode BLADE_INLINE_PHP; + +D_ENDPHP : '@endphp'->popMode; + +BLADE_PHP_INLINE : ~[@]+; +BLADE_PHP_INLINE_MORE : . ->type(BLADE_PHP_INLINE); + +EXIT_INLINE_PHP_EOF : EOF->type(ERROR),popMode; + +//========================================================= +// @verbatim +mode VERBATIM_MODE; + +D_ENDVERBATIM_IN_MODE : '@endverbatim'->type(DIRECTIVE), popMode; + +//hack to merge all php inputs into one token +VERBATIM_HTML : ~[@]+ ->type(HTML); +VERBATIM_HTML_MORE : . ->type(HTML); + +EXIT_VERBATIM_MOD_EOF : EOF->type(ERROR),popMode; + +//========================================================= +mode INSIDE_HTML_COMPONENT_TAG; + +COMPONENT_ATTRIBUTE : (':' FullIdentifier '="') {this.compAttrQuoteBalance = 1;} ->type(COMPONENT_ATTR),pushMode(COMPONENT_PHP_EXPRESSION); + +COMPONENT_CONTENT_TAG_OPEN : '{{' ->pushMode(INSIDE_REGULAR_ECHO),type(CONTENT_TAG); +COMPONENT_RAW_TAG_OPEN : '{!!' ->pushMode(INSIDE_RAW_ECHO),type(RAW_TAG); + +EXIT_HTML_COMPONENT : '>' {this.insideComponentTag = false;}->type(HTML), popMode; + +HTML_COMPONENT_ANY : . ->type(HTML); + +EXIT_HTML_COMPONENT_EOF : EOF->type(ERROR),popMode; + +//========================================================= +mode COMPONENT_PHP_EXPRESSION; + + +EXIT_COMPONENT_PHP_EXPRESSION : {this.compAttrQuoteBalance == 1}? '"' {this.compAttrQuoteBalance = 0;}->type(COMPONENT_ATTR), popMode; +COMPONENT_QUOTE_ATTR : '"' ->type(COMPONENT_ATTR); +COMPONENT_PHP_EXPRESSION_LAST : . ->type(PHP_EXPRESSION); + +EXIT_COMPONENT_PHP_EXPRESSION_EOF : EOF->type(ERROR),popMode; + +//========================================================= +mode ADIACENT_DIRECTIVE_TOKENS; + +TOKEN_ADIACENT_DIRECTIVE : (' ' | '>' | [\n\r] | '"')->type(D_UNKNOWN),popMode; + +TOKEN_ADIACENT_DIRECTIVE_OTHER : . ->type(HTML),popMode; +TOKEN_ADIACENT_DIRECTIVE_EOF : EOF->type(ERROR),popMode; \ No newline at end of file diff --git a/php/php.blade/src/org/netbeans/modules/php/blade/syntax/antlr4/v10/BladeAntlrLexer.g4 b/php/php.blade/src/org/netbeans/modules/php/blade/syntax/antlr4/v10/BladeAntlrLexer.g4 new file mode 100644 index 000000000000..bad8c287bbfa --- /dev/null +++ b/php/php.blade/src/org/netbeans/modules/php/blade/syntax/antlr4/v10/BladeAntlrLexer.g4 @@ -0,0 +1,452 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +lexer grammar BladeAntlrLexer; +import BladeCommonLexer; + +@header{ + /* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + + package org.netbeans.modules.php.blade.syntax.antlr4.v10; +} + +@lexer::members { + int rparenBalance = 0; + int sqparenBalance = 0; + int curlyparenBalance = 0; + int htmlCurlyParenBalance = 0; +} + +options { + superClass = LexerAdaptor; + caseInsensitive = true; +} + +channels {PHP_CODE} + +tokens { + LPAREN, + RPAREN, + D_CUSTOM, + D_DIRECTIVE, + IDENTIFIABLE_STRING, + BLADE_CONTENT_CLOSE_TAG, + LSQUAREBRACKET, + RSQUAREBRACKET, + LCURLYBRACE, + RCURLYBRACE, + D_PHP, + COMMA, + ERROR +} + +//conditionals +D_IF : '@if' (' ')* {lookupMode(INSIDE_PHP_EXPRESSION);}; +D_ELSEIF : '@elseif' (' ')* {lookupMode(INSIDE_PHP_EXPRESSION);}; +D_ELSE : '@else'; +D_ENDIF : '@endif'; + +D_UNLESS: '@unless' (' ')* {lookupMode(INSIDE_PHP_EXPRESSION);}; +D_ENDUNLESS: '@endunless'; + + +D_ISSET: '@isset' (' ')* {lookupMode(INSIDE_PHP_EXPRESSION);}; +D_ENDISSET: '@endisset'; + +D_SWITCH : '@switch' (' ')* {lookupMode(INSIDE_PHP_EXPRESSION);}; +D_CASE : '@case' (' ')* {lookupMode(INSIDE_PHP_EXPRESSION);}; +D_DEFAULT : '@default'; +D_ENDSWITCH : '@endswitch'; + +//layouts +D_EXTENDS : '@extends' (' ')* {lookupMode(PHP_EXPR_WITH_FIRST_IDENTIFIABLE_STRING);}; +D_INCLUDE : '@include' (' ')* {lookupMode(PHP_EXPR_WITH_FIRST_IDENTIFIABLE_STRING);}; +D_INCLUDE_IF : '@includeIf' (' ')* {lookupMode(PHP_EXPR_WITH_FIRST_IDENTIFIABLE_STRING);}; +D_INCLUDE_WHEN : '@includeWhen' (' ')* {this.identifierStringPos = 2; lookupMode(PHP_EXPR_WITH_CUSTOM_IDENTIFIABLE_STRING_POS);}; +D_INCLUDE_UNLESS : '@includeUnless' (' ')* {this.identifierStringPos = 2; lookupMode(PHP_EXPR_WITH_CUSTOM_IDENTIFIABLE_STRING_POS);}; +D_INCLUDE_FIRST : '@includeFirst' (' ')* {lookupMode(INCLUDE_FIRST_MODE);}; +D_EACH : '@each' (' ')* {lookupMode(PHP_EXPR_EACH);}; + +D_YIELD : '@yield' (' ')* {lookupMode(PHP_EXPR_WITH_FIRST_IDENTIFIABLE_STRING);}; +D_SECTION : '@section' (' ')* {lookupMode(PHP_EXPR_WITH_FIRST_IDENTIFIABLE_STRING);}; +D_HAS_SECTION : '@hasSection' (' ')* {lookupMode(PHP_EXPR_WITH_FIRST_IDENTIFIABLE_STRING);}; +D_SECTION_MISSING : '@sectionMissing' (' ')* {lookupMode(PHP_EXPR_WITH_FIRST_IDENTIFIABLE_STRING);}; +D_ENDSECTION : '@endsection'; + +D_PARENT : '@parent'; +D_SHOW : '@show'; +D_OVERWRITE : '@overwrite'; +D_STOP : '@stop'; +D_APPEND : '@append'; +D_ONCE : '@once'; +D_ENDONCE : '@endonce'; + +D_STACK : '@stack' (' ')* {lookupMode(PHP_EXPR_WITH_FIRST_IDENTIFIABLE_STRING);}; +D_PUSH : '@push' (' ')* {lookupMode(PHP_EXPR_WITH_FIRST_IDENTIFIABLE_STRING);}; +D_ENDPUSH : '@endpush'; +D_PUSH_IF : '@pushIf' (' ')* {lookupMode(PHP_EXPR_WITH_FIRST_IDENTIFIABLE_STRING);}; +D_ENDPUSH_IF : '@endPushIf'; +D_PUSH_ONCE : '@pushOnce' (' ')* {lookupMode(PHP_EXPR_WITH_FIRST_IDENTIFIABLE_STRING);}; +D_ENDPUSH_ONCE : '@endPushOnce'; +D_PREPEND : '@prepend' (' ')* {lookupMode(PHP_EXPR_WITH_FIRST_IDENTIFIABLE_STRING);}; +D_ENDPREPEND : '@endprepend'; + + +//loops +D_FOREACH : '@foreach' (' ')* {lookupMode(FOREACH_PHP_EXPR);}; +D_ENDFOREACH : '@endforeach'; + +D_FOR : '@for' (' ')* {lookupMode(INSIDE_PHP_EXPRESSION);}; +D_ENDFOR : '@endfor'; + +D_FORELSE : '@forelse' (' ')* {lookupMode(INSIDE_PHP_EXPRESSION);}; +D_ENDFORELSE : '@endforelse'; + +D_WHILE : '@while' (' ')* {lookupMode(INSIDE_PHP_EXPRESSION);}; +D_ENDWHILE : '@endwhile'; + +D_BREAK : '@break' (' ')* {flexibleMode(INSIDE_PHP_EXPRESSION);}; +D_CONTINUE : '@continue' (' ')* {flexibleMode(INSIDE_PHP_EXPRESSION);}; + +//context + +D_EMPTY : '@empty' (' ')* {flexibleMode(INSIDE_PHP_EXPRESSION);}; +D_ENDEMPTY: '@endempty'; +D_VERBATIM : '@verbatim' ->pushMode(VERBATIM_MODE); +D_ENDVERBATIM : '@endverbatim'; + +D_SESSION : '@session' (' ')* {lookupMode(INSIDE_PHP_EXPRESSION);}; +D_ENDSESSION : '@endsession'; + +//forms +D_CSRF : '@csrf'; +D_METHOD : '@method' (' ')* {lookupMode(INSIDE_PHP_EXPRESSION);}; +D_ERROR : '@error' (' ')* {lookupMode(INSIDE_PHP_EXPRESSION);}; +D_ENDERROR : '@enderror'; + +//authentification + +D_AUTH : '@auth' (' ')* {flexibleMode(INSIDE_PHP_EXPRESSION);}; +D_ENDAUTH : '@endauth'; + +D_GUEST : '@guest' (' ')* {flexibleMode(INSIDE_PHP_EXPRESSION);}; +D_ELSEGUEST : '@elseguest'; +D_ENDGUEST : '@endguest'; + +D_ENV : '@env' (' ')* {lookupMode(INSIDE_PHP_EXPRESSION);}; +D_ENDENV : '@endenv'; + +D_PRODUCTION : '@production'; +D_ENDPRODUCTION : '@endproduction'; + +//permission +D_CAN : '@can' (' ')* {lookupMode(INSIDE_PHP_EXPRESSION);}; +D_ENDCAN : '@endcan'; + + +D_CANNOT : '@cannot' (' ')* {lookupMode(INSIDE_PHP_EXPRESSION);}; +D_CANANY : '@canany' (' ')* {lookupMode(INSIDE_PHP_EXPRESSION);}; +D_ELSECAN : '@elsecan' ('not' | 'any')? (' ')* {lookupMode(INSIDE_PHP_EXPRESSION);}; +D_ENDCANNOT : '@endcannot'; +D_ENDCANANY : '@endcanany'; + +//blocks + +D_FRAGMENT : '@fragment' (' ')* {lookupMode(INSIDE_PHP_EXPRESSION);}; +D_ENDFRAGMENT: '@endfragment'; + +D_PHP_INLINE : '@php' (' ')* {this._input.LA(1) == '('}? ->type(D_PHP),pushMode(INSIDE_PHP_EXPRESSION); + +D_PHP : '@php' [ \r\n] ->pushMode(BLADE_INLINE_PHP); + +//misc +D_SIMPLE_DIRECTIVE : ('@dd' | '@dump' | '@json' | '@style' | '@class' +| '@checked' | '@disabled' | '@selected' | '@required' | '@readonly' +| '@when' | '@bool') (' ')* {lookupMode(INSIDE_PHP_EXPRESSION);}; + +D_PROPS : '@props' (' ')* {lookupMode(INSIDE_PHP_EXPRESSION);}; + +D_VITE : '@vite' (' ')* {lookupMode(MIXED_STRING_AND_ARRAY_IDENTIFIER);}; +D_VITE_REFRESH : '@viteReactRefresh'; + +D_LANG : '@lang' {lookupMode(PHP_EXPR_WITH_FIRST_IDENTIFIABLE_STRING);}; + +D_INJECT : '@inject' (' ')* {this.identifierStringPos = 2; lookupMode(PHP_EXPR_WITH_CUSTOM_IDENTIFIABLE_STRING_POS);}; +D_USE : '@use' {lookupMode(PHP_EXPR_WITH_FIRST_IDENTIFIABLE_STRING);}; + +//spatie +D_LIVEWIRE_ARG : '@livewire' (' ')* {lookupMode(INSIDE_PHP_EXPRESSION);}->type(D_SIMPLE_DIRECTIVE); +D_LIVEWIRE : ('@livewireStyles' | '@bukStyles' | '@livewireScripts' | '@bukScripts' | '@click' ('.away')? '=')->type(D_DIRECTIVE); + + + +//extra + +BLADE_CONTENT_OPEN_TAG : '{{' {htmlCurlyParenBalance=0;}; +BLADE_TAG_ESCAPE : '@' ('{')+->skip; +//avoid curly closing + +BLADE_CONTENT_CLOSE_TAG : '}}' {this.consumeCloseTag(htmlCurlyParenBalance);} ; + +BLADE_RAW_OPEN_TAG : '{!!'; +BLADE_RAW_CLOSE_TAG : '!!}'; + +URI_PATH_PART : '/' '@' Identifier '/'->skip; + +D_CSS_AT : CssAtWithArg->skip; +D_CSS_AT2 : CssAtWithArg (' ')* {this._input.LA(1) == '('}?->skip; +D_CUSTOM : '@' Identifier (' ')* {this._input.LA(1) == '('}?; + +D_ENDCUSTOM : '@end' Identifier; +D_CUSTOM_SIMPLE : '@' Identifier ->type(D_CUSTOM); + +AT : '@' ->skip; + +COMMA : ',' ->skip; + +//for simpler syntax on parser +LPAREN : '('->skip; +RPAREN : ')'->skip; + +LSQUAREBRACKET: '['->skip; +RSQUAREBRACKET: ']'->skip; + +LCURLYBRACE: '{' {htmlCurlyParenBalance++;}->skip; +RCURLYBRACE: '}' {htmlCurlyParenBalance--;}->skip; + +PHP_INLINE_START : ('pushMode(INSIDE_PHP_INLINE); + +HTML_COMPONENT_OPEN_TAG : 'skip; + +OTHER : . ->skip; + + +//========================================== +//MODES + +mode INSIDE_PHP_EXPRESSION; + +START_E_LPAREN: '(' {rparenBalance == 0}? {rparenBalance++;}->type(LPAREN); +E_LPAREN: '(' {rparenBalance++;}->skip; +EXIT_E_RPAREN: ')' {rparenBalance == 1}? {rparenBalance--;} ->type(RPAREN),mode(DEFAULT_MODE); +E_RPAREN: ')' {rparenBalance--;} ->skip; + +E_LSQUAREBRACKET: '[' {sqparenBalance++;} ->skip; +E_RSQUAREBRACKET: ']' {sqparenBalance--;}->skip; + +E_LCURLYBRACE: '{' {curlyparenBalance++;}->skip; +E_RCURLYBRACE: '}' {curlyparenBalance--;}->skip; + +E_ARG_COMMA : ',' {rparenBalance == 1 && sqparenBalance == 0 && sqparenBalance == 0}? ->type(COMMA); + +E_OTHER : . ->skip; + +EXIT_E_EOF : EOF->type(ERROR),mode(DEFAULT_MODE); + +//========================================== +mode PHP_EXPR_WITH_FIRST_IDENTIFIABLE_STRING; + +EI_IDENTIFIABLE_STRING : (' ')* STRING_LITERAL (' ')* + {this._input.LA(1) == ',' || this._input.LA(1) == ')'}? ->type(IDENTIFIABLE_STRING),mode(INSIDE_PHP_EXPRESSION); + +START_EI_LPAREN: '(' {rparenBalance == 0}? {rparenBalance++;}->type(LPAREN); +EI_LPAREN: '(' {rparenBalance++;}->skip; +EXIT_EI_RPAREN: ')' {rparenBalance == 1}? {rparenBalance--;} ->type(RPAREN),mode(DEFAULT_MODE); +EI_RPAREN: ')' {rparenBalance--;} ->skip,mode(INSIDE_PHP_EXPRESSION); + +EI_OTHER : . ->skip, mode(INSIDE_PHP_EXPRESSION); + +EXIT_EI_EOF : EOF->type(ERROR),mode(DEFAULT_MODE); + +//========================================== +mode PHP_EXPR_WITH_CUSTOM_IDENTIFIABLE_STRING_POS; + +START_ESPOS_LPAREN: '(' {rparenBalance == 0}? {rparenBalance++; this.argCounter=1;}->type(LPAREN); +ESPOS_LPAREN: '(' {rparenBalance++;}->skip; +EXIT_ESPOS_RPAREN: ')' {rparenBalance == 1}? {rparenBalance--;} ->type(RPAREN),mode(DEFAULT_MODE); +ESPOS_RPAREN: ')' {rparenBalance--;} ->skip; + +ESPOS_LSQUAREBRACKET: '[' {sqparenBalance++;} ->skip; +ESPOS_RSQUAREBRACKET: ']' {sqparenBalance--;}->skip; + +ESPOS_LCURLYBRACE: '{' {curlyparenBalance++;}->skip; +ESPOS_RCURLYBRACE: '}' {curlyparenBalance--;}->skip; + +ESPOS_ARG_COMMA : ',' {rparenBalance == 1 && sqparenBalance == 0 && curlyparenBalance == 0}? {this.argCounter++;} ->type(COMMA); + +ESPOS_IDENTIFIABLE_STRING : (' ')* STRING_LITERAL (' ')* + {(this._input.LA(1) == ',' || this._input.LA(1) == ')') + && this.argCounter == this.identifierStringPos }? { this.identifierStringPos = 0; } ->type(IDENTIFIABLE_STRING); + +ESPOS_OTHER : . ->skip; + +EXIT_ESPOS_EOF : EOF->type(ERROR),mode(DEFAULT_MODE); + +//========================================== +//@each +mode PHP_EXPR_EACH; + +START_EACH_LPAREN: '(' {rparenBalance == 0}? {rparenBalance++;this.argCounter=1;}->type(LPAREN); +EACH_LPAREN: '(' {rparenBalance++;}->skip; +EXIT_EACH_RPAREN: ')' {rparenBalance == 1}? {rparenBalance--;} ->type(RPAREN),mode(DEFAULT_MODE); +EACH_RPAREN: ')' {rparenBalance--;} ->skip; + +EACH_LSQUAREBRACKET: '[' {sqparenBalance++;} ->skip; +EACH_RSQUAREBRACKET: ']' {sqparenBalance--;}->skip; + +EACH_LCURLYBRACE: '{' {curlyparenBalance++;}->skip; +EACH_RCURLYBRACE: '}' {curlyparenBalance--;}->skip; + +EACH_ARG_COMMA : ',' {rparenBalance == 1 && sqparenBalance == 0 && sqparenBalance == 0}? {this.argCounter++;} ->type(COMMA); + +EACH_IDENTIFIABLE_STRING : (' ')* STRING_LITERAL (' ')* + {this.argCounter == 1 && this.identifierStringPos == 1 }? { this.identifierStringPos = 4; } ->type(IDENTIFIABLE_STRING); + +EACH_LAST_IDENTIFIABLE_STRING : (' ')* STRING_LITERAL (' ')* + {this._input.LA(1) == ')' && this.argCounter == 4 && this.identifierStringPos == 4 }? { this.identifierStringPos = 0; } ->type(IDENTIFIABLE_STRING); + +EACH_OTHER : . ->skip; + +EXIT_EACH_EOF : EOF->type(ERROR),mode(DEFAULT_MODE); + +//========================================== +//@includeFirst +mode INCLUDE_FIRST_MODE; + +START_INCF_LPAREN: '(' {rparenBalance == 0}? {rparenBalance++;this.argCounter=1;}->type(LPAREN); +INCF_LPAREN: '(' {rparenBalance++;}->skip; +EXIT_INCF_RPAREN: ')' {rparenBalance == 1}? {rparenBalance--;} ->type(RPAREN),mode(DEFAULT_MODE); +INCF_RPAREN: ')' {rparenBalance--;} ->skip; + +INCF_LSQUAREBRACKET: '[' {sqparenBalance++;} ->skip; +INCF_RSQUAREBRACKET: ']' {sqparenBalance--;}->skip; + +INCF_LCURLYBRACE: '{' {curlyparenBalance++;}->skip; +INCF_RCURLYBRACE: '}' {curlyparenBalance--;}->skip; + +INCF_ARG_COMMA : ',' {rparenBalance == 1 && sqparenBalance == 0 && curlyparenBalance == 0}? {this.argCounter++;} ->type(COMMA); + +INCF_IDENTIFIABLE_STRING : (' ')* STRING_LITERAL (' ')* + {rparenBalance == 1 && sqparenBalance == 1 && this.argCounter == 1}? ->type(IDENTIFIABLE_STRING) + ; + +INCF_OTHER : . ->skip; + +EXIT_INCF_EOF : EOF->type(ERROR),mode(DEFAULT_MODE); + +//========================================== +mode MIXED_STRING_AND_ARRAY_IDENTIFIER; + +START_MIXED_S_A_LPAREN: '(' {rparenBalance == 0}? {rparenBalance++;}->type(LPAREN); +MIXED_S_A_LPAREN: '(' {rparenBalance++;}->skip; +EXIT_MIXED_S_A_RPAREN: ')' {rparenBalance == 1}? {rparenBalance--;} ->type(RPAREN),mode(DEFAULT_MODE); +MIXED_S_A_RPAREN: ')' {rparenBalance--;} ->skip; + +MIXED_S_A_LSQUAREBRACKET: '[' {sqparenBalance++;} ->skip; +MIXED_S_A_RSQUAREBRACKET: ']' {sqparenBalance--;}->skip; + +MIXED_S_A_LCURLYBRACE: '{' {curlyparenBalance++;}->skip; +MIXED_S_A_RCURLYBRACE: '}' {curlyparenBalance--;}->skip; + +MIXED_S_A_ARG_COMMA : ',' {rparenBalance == 1 && sqparenBalance == 1 && curlyparenBalance == 0}? ->type(COMMA); + +MIXED_S_A_IDENTIFIABLE_STRING : (' ')* STRING_LITERAL (' ')* + {rparenBalance == 1}? ->type(IDENTIFIABLE_STRING) + ; + +MIXED_S_A_OTHER : . ->skip; + +EXIT_MIXED_S_A_EOF : EOF->type(ERROR),mode(DEFAULT_MODE); + +//========================================== +//@foreach expr +mode FOREACH_PHP_EXPR; + +START_FOREACH_LPAREN: '(' {rparenBalance == 0}? {rparenBalance++;}->type(LPAREN); +FOREACH_LPAREN: '(' {rparenBalance++;}->skip; +EXIT_FOREACH_RPAREN: ')' {rparenBalance == 1}? {rparenBalance--;} ->type(RPAREN),mode(DEFAULT_MODE); +FOREACH_RPAREN: ')' {rparenBalance--;} ->skip; + +FOREACH_LSQUAREBRACKET: '[' {sqparenBalance++;} ->skip; +FOREACH_RSQUAREBRACKET: ']' {sqparenBalance--;}->skip; + +FOREACH_LCURLYBRACE: '{' {curlyparenBalance++;}->skip; +FOREACH_RCURLYBRACE: '}' {curlyparenBalance--;}->skip; + +FOREACH_VAR : '$' Identifier {rparenBalance == 1}?; //not in argument + +FOREACH_PHP_EXPR_STRING : STRING_LITERAL->skip; + +FOREACH_AS : 'as'; + +FOREACH_DOUBLE_ARROW : '=>' {rparenBalance == 1 && sqparenBalance == 0 && curlyparenBalance == 0}?; + +FOREACH_DENTIFIER : Identifier ->skip; + +FOREACH_OTHER : . ->skip; + +EXIT_FOREACH_EOF : EOF->type(ERROR),mode(DEFAULT_MODE); + +//========================================== +//@php @endphp +mode BLADE_INLINE_PHP; + +D_ENDPHP : '@endphp'->popMode; + +BLADE_INLINE_PHP_OTHER : . ->skip; + +EXIT_BLADE_INLINE_PHP : EOF->type(ERROR),mode(DEFAULT_MODE); + +//========================================== +//@verbatim +mode VERBATIM_MODE; + +D_ENDVERBATIM_IN_MODE : '@endverbatim'->type(D_ENDVERBATIM), mode(DEFAULT_MODE); + +VERBATIM_HTML : . ->skip; + + +EXIT_VERBATIM_MOD_EOF : EOF->type(ERROR),mode(DEFAULT_MODE); + +//========================================== +mode INSIDE_PHP_INLINE; + +PHP_INLINE_EXIT : '?>'->popMode; + +PHP_INLINE_OTHER : . ->skip; + +PHP_INLINE_EOF : EOF->mode(DEFAULT_MODE); \ No newline at end of file diff --git a/php/php.blade/src/org/netbeans/modules/php/blade/syntax/antlr4/v10/BladeAntlrParser.g4 b/php/php.blade/src/org/netbeans/modules/php/blade/syntax/antlr4/v10/BladeAntlrParser.g4 new file mode 100644 index 000000000000..611c49f85975 --- /dev/null +++ b/php/php.blade/src/org/netbeans/modules/php/blade/syntax/antlr4/v10/BladeAntlrParser.g4 @@ -0,0 +1,208 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +parser grammar BladeAntlrParser; + +@header{ + /* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + package org.netbeans.modules.php.blade.syntax.antlr4.v10; +} + +@parser::members {public static int bladeVersion = 10;} + +options { tokenVocab = BladeAntlrLexer; } + +file : (statement)* EOF; + +statement : + blockIdentifiableArgDirective + | blockDirective + | identifiableArgDirective + | multipleArgDirective + | inlineDirective + | customDirective + | bladeContentTags + | bladeRawTags + | ('}}' | '!!}') //stray tags are possible + | errorDirectives + | phpInline + | D_ENDCUSTOM + | htmlComponentOpenTag +; + +blockDirective : + ifStatement + | foreachStatement + | forStatement + | forElseStatement + | D_WHILE directiveArguments (statement | loopBreaks)* D_ENDWHILE + | switchStatement + | formStatements + | authStatements + | D_ONCE directiveArguments? statement* D_ENDONCE + | D_UNLESS directiveArguments statement* D_ENDUNLESS + | D_EMPTY directiveArguments statement* (D_ELSE statement*)? D_ENDEMPTY + | D_ISSET directiveArguments statement* (D_ELSE statement*)? D_ENDISSET + | D_SESSION directiveArguments statement* D_ENDSESSION + //permission + | D_CAN directiveArguments statement* (D_ELSECAN directiveArguments statement*)* (D_ELSE statement*)? D_ENDCAN + | D_CANNOT directiveArguments statement* (D_ELSECAN directiveArguments statement*)* (D_ELSE statement*)? D_ENDCANNOT + | D_CANANY directiveArguments statement* (D_ELSECAN directiveArguments statement*)* (D_ELSE statement*)? D_ENDCANANY + | bladePhpBlock + //x.11 + | D_FRAGMENT directiveArguments statement* D_ENDFRAGMENT + | D_VERBATIM D_ENDVERBATIM + | D_ELSE statement* D_ENDCUSTOM + +; + +ifStatement : ifStartStatement + (elseifStatement | statement | loopBreaks)* + (D_ELSE (statement | loopBreaks)*)? + D_ENDIF; + +foreachStatement : D_FOREACH foreachLoopArguments + (statement | loopBreaks)* D_ENDFOREACH; + +forStatement : D_FOR directiveArguments + (statement | loopBreaks)* D_ENDFOR; + +forElseStatement : D_FORELSE directiveArguments (statement | loopBreaks)* + D_EMPTY statement* D_ENDFORELSE; + +ifStartStatement : D_IF directiveArguments; +elseifStatement: D_ELSEIF directiveArguments statement*; + +switchStatement : + D_SWITCH directiveArguments + (D_CASE directiveArguments statement* D_BREAK?)* + (D_DEFAULT statement*)? + D_ENDSWITCH + ; + +bladePhpBlock : + D_PHP D_ENDPHP + ; + +// +inlineDirective: + D_SIMPLE_DIRECTIVE directiveArguments + | D_PROPS directiveArguments + | D_PHP directiveArguments + | D_DIRECTIVE + | D_VITE_REFRESH +; + +identifiableArgDirective : + D_SECTION '(' IDENTIFIABLE_STRING? ',' ')' + | D_EXTENDS '(' IDENTIFIABLE_STRING? (',')? ')' + | (D_INCLUDE | D_INCLUDE_IF) '(' IDENTIFIABLE_STRING? (',')? ')' + | (D_INCLUDE_WHEN | D_INCLUDE_UNLESS) '(' ',' IDENTIFIABLE_STRING? (',')? ')' + | D_YIELD '(' IDENTIFIABLE_STRING? ','? ')' + | D_STACK '(' IDENTIFIABLE_STRING? ')' + | D_LANG '(' IDENTIFIABLE_STRING? ','? ')' + | D_INJECT '(' ',' IDENTIFIABLE_STRING? ')' + | D_USE '(' IDENTIFIABLE_STRING? (',')? ')' +; + +multipleArgDirective : + D_EACH '(' IDENTIFIABLE_STRING? ',' ',' (',' IDENTIFIABLE_STRING?)? ')' + | D_INCLUDE_FIRST '(' IDENTIFIABLE_STRING* (',')? ')' + | D_VITE '(' IDENTIFIABLE_STRING? (',' IDENTIFIABLE_STRING?)* ')' +; + +blockIdentifiableArgDirective : + D_SECTION '(' IDENTIFIABLE_STRING? ')' D_PARENT? statement* D_PARENT? (D_SHOW | D_STOP | D_OVERWRITE | D_ENDSECTION | D_APPEND) + | D_HAS_SECTION '(' IDENTIFIABLE_STRING? ')' statement* D_ENDIF + | D_SECTION_MISSING '(' IDENTIFIABLE_STRING? ')' statement* D_ENDIF + | D_PUSH '(' IDENTIFIABLE_STRING? ')' statement* D_ENDPUSH + | D_PUSH_IF '(' IDENTIFIABLE_STRING? ',' ')' statement* D_ENDPUSH_IF + | D_PUSH_ONCE '(' IDENTIFIABLE_STRING? ')' statement* D_ENDPUSH_ONCE + | D_PREPEND '(' IDENTIFIABLE_STRING? ')' statement* D_ENDPREPEND +; + +directiveWithArg : + D_EXTENDS + | D_INCLUDE + | D_YIELD +; + +customDirective : + D_CUSTOM directiveArguments?; + +loopBreaks : + (D_CONTINUE | D_BREAK) directiveArguments?; + + +formStatements: + D_ERROR '(' ','? ')' statement* (D_ELSE statement*)? D_ENDERROR + | D_CSRF + | D_METHOD directiveArguments + ; + +authStatements: + D_AUTH directiveArguments? statement* ((D_ELSEGUEST | D_ELSE) statement*)? D_ENDAUTH + | D_GUEST directiveArguments? statement* D_ENDGUEST + | D_ENV directiveArguments statement* D_ENDENV + | D_PRODUCTION statement* D_ENDPRODUCTION +; + +bladeContentTags: + '{{' '}}' +; + +bladeRawTags: + '{!!' '!!}' +; + +errorDirectives: + directiveWithArg '(' {notifyErrorListeners("Missing closing ')'");} + | D_IF directiveArguments {notifyErrorListeners("Syntax error, expecting @elseif or @else or @endif");} + | ( D_FOREACH foreachLoopArguments | D_FOR directiveArguments ) {notifyErrorListeners("Unclosed block directive");} + | D_VERBATIM {notifyErrorListeners("Unclosed verbatim block");} + | D_SECTION '(' IDENTIFIABLE_STRING? ')' {notifyErrorListeners("Inline @section requires second argument");} +; + +directiveArguments : + '(' ','* ')'; + +foreachLoopArguments : '(' main_array=FOREACH_VAR 'as' array_item=FOREACH_VAR (FOREACH_DOUBLE_ARROW array_value=FOREACH_VAR)? ')' + | '(' (FOREACH_VAR)* 'as' (FOREACH_VAR)* (FOREACH_DOUBLE_ARROW FOREACH_VAR*)? ')';//unscanable loop + +phpInline: + PHP_INLINE_START phpInlineEnd=(PHP_INLINE_EXIT | EOF); + +htmlComponentOpenTag: + HTML_COMPONENT_OPEN_TAG; \ No newline at end of file diff --git a/php/php.blade/src/org/netbeans/modules/php/blade/syntax/antlr4/v10/BladeColoringCommonLexer.g4 b/php/php.blade/src/org/netbeans/modules/php/blade/syntax/antlr4/v10/BladeColoringCommonLexer.g4 new file mode 100644 index 000000000000..cf52f79d4ab5 --- /dev/null +++ b/php/php.blade/src/org/netbeans/modules/php/blade/syntax/antlr4/v10/BladeColoringCommonLexer.g4 @@ -0,0 +1,87 @@ + /* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +lexer grammar BladeCommonLexer; + +tokens { + HTML, + BLADE_COMMENT +} + +fragment NameString + : [a-z_\u0080-\ufffe][a-z0-9_\u0080-\ufffe]*; + +fragment BladeLabel + : [a-z\u0080-\ufffe][a-z0-9_.\u0080-\ufffe]*; + +fragment FullIdentifier + : [a-z_\u0080-\ufffe][a-z0-9_\u0080-\ufffe-]*; + +fragment ESC_DOUBLE_QUOTED_STRING + : [\\"]; + +fragment DOUBLE_QUOTED_STRING_FRAGMENT + : '"' (ESC_DOUBLE_QUOTED_STRING | . )*? '"'; + +fragment SINGLE_QUOTED_STRING_FRAGMENT + : '\'' (~('\'' | '\\') | '\\' . )* '\''; + +fragment LineComment + : '//' ~ [\r\n]* + ; + +fragment PhpVariable + : '$' NameString; + +fragment PhpKeyword + : 'array' | 'class' | 'empty' | 'use'; + +fragment Digit + : ('0'..'9'); + +BLADE_COMMENT_START : '{{--' ->pushMode(INSIDE_BLADE_COMMENT); + +EMAIL_SUBSTRING : ('@' FullIdentifier '.')->type(HTML); + +VERSION_WITH_AT: '@' (Digit '.'?)+->type(HTML); + +//escapes +D_ESCAPES + : ( + '{{{' + | '@@' '@'? + | '@{' '{'? + | '@media' [ ]+ ('screen' [ ]+ 'and'?)? + | '@media' (' ')* {this._input.LA(1) == '('}? + | ( '@charset' | '@import' | '@namespace' | '@document' | '@font-face' + | '@page' | '@layer' | '@supports' | '@tailwind' | '@apply' | '@-webkit-keyframes' + | '@keyframes' | '@counter-style' | '@font-feature-values' | '@property' + | '@scope' | '@starting-style' | '@view-transition' | '@container' + | '@color-profile' | '@styleset' | '@font-palette-values' | '@media' + ) [ ]* + )->type(HTML); + +mode INSIDE_BLADE_COMMENT; + +BLADE_COMMENT_END : '--}}'->popMode; + +//hack to merge all php inputs into one token +BLADE_COMMENT_GREEDY : ~[-]+ ->type(BLADE_COMMENT); +BLADE_COMMENT_MORE : . ->type(BLADE_COMMENT); + +BLADE_COMMENT_EOF : EOF->type(BLADE_COMMENT),popMode; \ No newline at end of file diff --git a/php/php.blade/src/org/netbeans/modules/php/blade/syntax/antlr4/v10/BladeCommonLexer.g4 b/php/php.blade/src/org/netbeans/modules/php/blade/syntax/antlr4/v10/BladeCommonLexer.g4 new file mode 100644 index 000000000000..9a6c6664f909 --- /dev/null +++ b/php/php.blade/src/org/netbeans/modules/php/blade/syntax/antlr4/v10/BladeCommonLexer.g4 @@ -0,0 +1,70 @@ + /* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +lexer grammar BladeCommonLexer; + +fragment Identifier + : [a-z_\u0080-\ufffe][a-z0-9_\u0080-\ufffe]*; + +fragment HtmlIdentifier + : [a-z_\u0080-\ufffe][a-z0-9_\u0080-\ufffe-]*; + +fragment ESC_DOUBLE_QUOTED_STRING + : [\\"]; + +fragment DOUBLE_QUOTED_STRING_FRAGMENT + : '"' (ESC_DOUBLE_QUOTED_STRING | . )*? '"'; + +fragment SINGLE_QUOTED_STRING_FRAGMENT + : '\'' (~('\'' | '\\') | '\\' . )* '\''; + +fragment STRING_LITERAL : DOUBLE_QUOTED_STRING_FRAGMENT | SINGLE_QUOTED_STRING_FRAGMENT; + +fragment Digit + : ('0'..'9'); + +fragment CssAtWithArg : '@media' | '@supports' | '@container'; + +BLADE_COMMENT_START : '{{--' ->pushMode(INSIDE_BLADE_COMMENT), skip; + +EMAIL_SUBSTRING : ('@' Identifier '.')->skip; + +VERSION_WITH_AT: '@' (Digit '.'?)+->skip; + +//escapes +D_ESCAPES + : ( + '{{{' + | '@@' '@'? + | '@media' [ ]+ ('screen' [ ]+ 'and'?)? + | '@media' (' ')* {this._input.LA(1) == '('}? + | ( '@charset' | '@import' | '@namespace' | '@document' | '@font-face' + | '@page' | '@layer' | '@supports' | '@tailwind' | '@apply' | '@-webkit-keyframes' + | '@keyframes' | '@counter-style' | '@font-feature-values' | '@property' + | '@scope' | '@starting-style' | '@supports' | '@view-transition' + | '@container' | '@color-profile' | '@styleset' | '@font-palette-values' | '@media' + ) [ ]* + )->skip; + +mode INSIDE_BLADE_COMMENT; + +BLADE_COMMENT_END : '--}}'->popMode, skip; + +BLADE_COMMENT_MORE : . ->skip; + +BLADE_COMMENT_EOF : EOF->popMode, skip; \ No newline at end of file diff --git a/php/php.blade/src/org/netbeans/modules/php/blade/syntax/antlr4/v10/ColoringLexerAdaptor.java b/php/php.blade/src/org/netbeans/modules/php/blade/syntax/antlr4/v10/ColoringLexerAdaptor.java new file mode 100644 index 000000000000..363e1cfc982d --- /dev/null +++ b/php/php.blade/src/org/netbeans/modules/php/blade/syntax/antlr4/v10/ColoringLexerAdaptor.java @@ -0,0 +1,80 @@ + /* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.php.blade.syntax.antlr4.v10; + +import org.antlr.v4.runtime.CharStream; +import org.antlr.v4.runtime.Lexer; +import org.antlr.v4.runtime.Token; + +/** + * + * @author bogdan + */ +public abstract class ColoringLexerAdaptor extends Lexer { + + private int _currentRuleType = Token.INVALID_TYPE; + public int rParenBalance = 0; + public int compAttrQuoteBalance = 0; + public boolean insideComponentTag = false; + + public ColoringLexerAdaptor(CharStream input) { + super(input); + } + + public int getCurrentRuleType() { + return _currentRuleType; + } + + public void setCurrentRuleType(int ruleType) { + this._currentRuleType = ruleType; + } + + @Override + public Token emit() { + return super.emit(); + } + + @Override + public void reset() { + //setCurrentRuleType(Token.INVALID_TYPE); + rParenBalance = 0; + compAttrQuoteBalance = 0; + insideComponentTag = false; + super.reset(); + } + + //blade coloring lexer + public void consumeEscapedEchoToken() { + if (this._input.LA(1) == '}' && this._input.LA(2) == '}') { + this.setType(BladeAntlrColoringLexer.BLADE_PHP_ECHO_EXPR); + } else { + this.more(); + } + } + + //blade coloring lexer + public void consumeNotEscapedEchoToken() { + if (this._input.LA(1) == '!' && this._input.LA(2) == '!' && this._input.LA(3) == '}') { + this.setType(BladeAntlrColoringLexer.BLADE_PHP_ECHO_EXPR); + } else { + this.more(); + } + } +} + diff --git a/php/php.blade/src/org/netbeans/modules/php/blade/syntax/antlr4/v10/LexerAdaptor.java b/php/php.blade/src/org/netbeans/modules/php/blade/syntax/antlr4/v10/LexerAdaptor.java new file mode 100644 index 000000000000..a8a92e67136b --- /dev/null +++ b/php/php.blade/src/org/netbeans/modules/php/blade/syntax/antlr4/v10/LexerAdaptor.java @@ -0,0 +1,65 @@ + /* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.php.blade.syntax.antlr4.v10; + +import org.antlr.v4.runtime.CharStream; +import org.antlr.v4.runtime.Lexer; + +/** + * + * @author bogdan + */ +public abstract class LexerAdaptor extends Lexer { + + public int exitIfModePosition = 0; + public boolean compomentTagOpen = false; + public int identifierStringPos = 1; + public int argCounter = 1; + + public LexerAdaptor(CharStream input) { + super(input); + } + + public void lookupMode(int mode){ + if (this._input.LA(1) == '('){ + this.mode(mode); + } else { + this.resetIdentifierStringPos(); + this.skip(); + } + } + + public void flexibleMode(int mode){ + if (this._input.LA(1) == '('){ + this.mode(mode); + } + } + + public void resetIdentifierStringPos(){ + this.identifierStringPos = 1; + } + + public void consumeCloseTag(int curlyBalance){ + if (curlyBalance == 0) { + this.setType(BladeAntlrLexer.BLADE_CONTENT_CLOSE_TAG); + } else { + this.skip(); + } + } +} diff --git a/php/php.blade/src/org/netbeans/modules/php/blade/syntax/php/PhpKeywordList.java b/php/php.blade/src/org/netbeans/modules/php/blade/syntax/php/PhpKeywordList.java new file mode 100644 index 000000000000..49cf02c8b0a6 --- /dev/null +++ b/php/php.blade/src/org/netbeans/modules/php/blade/syntax/php/PhpKeywordList.java @@ -0,0 +1,41 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.php.blade.syntax.php; + +import org.netbeans.modules.php.blade.syntax.annotation.PhpKeyword; +import org.netbeans.modules.php.blade.syntax.annotation.PhpKeywordRegister; + +/** + * + * @author bogdan + */ +@PhpKeywordRegister({ + //conditionals + @PhpKeyword(name = "empty", parenExpr = true), + @PhpKeyword(name = "isset", parenExpr = true), + @PhpKeyword(name = "class"), + @PhpKeyword(name = "echo"), +}) +public class PhpKeywordList { + + public PhpKeyword[] getKeywords() { + PhpKeywordRegister register = this.getClass().getAnnotation(PhpKeywordRegister.class); + return register.value(); + } +} diff --git a/php/php.blade/src/org/netbeans/modules/php/blade/ui/options/BladeOptionsPanel.form b/php/php.blade/src/org/netbeans/modules/php/blade/ui/options/BladeOptionsPanel.form new file mode 100644 index 000000000000..1fa124a33534 --- /dev/null +++ b/php/php.blade/src/org/netbeans/modules/php/blade/ui/options/BladeOptionsPanel.form @@ -0,0 +1,76 @@ + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/php/php.blade/src/org/netbeans/modules/php/blade/ui/options/BladeOptionsPanel.java b/php/php.blade/src/org/netbeans/modules/php/blade/ui/options/BladeOptionsPanel.java new file mode 100644 index 000000000000..bb35eedbb1e0 --- /dev/null +++ b/php/php.blade/src/org/netbeans/modules/php/blade/ui/options/BladeOptionsPanel.java @@ -0,0 +1,102 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.php.blade.ui.options; + +import org.netbeans.modules.php.api.util.UiUtils; +import org.netbeans.modules.php.blade.editor.preferences.GeneralPreferencesUtils; +import org.netbeans.modules.php.blade.editor.preferences.ModulePreferences; +import org.netbeans.spi.options.OptionsPanelController; + +/** + * + * @author bogdan + */ +@OptionsPanelController.Keywords( + keywords = { + "php" // NOI18N + }, + location = UiUtils.OPTIONS_PATH, + tabTitle = "Laravel tab" // NOI18N +) +public class BladeOptionsPanel extends javax.swing.JPanel { + + BladeOptionsPanel() { + initComponents(); + } + + /** + * This method is called from within the constructor to initialize the form. + * WARNING: Do NOT modify this code. The content of this method is always + * regenerated by the Form Editor. + */ + @SuppressWarnings("unchecked") + // //GEN-BEGIN:initComponents + private void initComponents() { + + jLabel1 = new javax.swing.JLabel(); + auto_tag_completion = new javax.swing.JCheckBox(); + + org.openide.awt.Mnemonics.setLocalizedText(jLabel1, org.openide.util.NbBundle.getMessage(BladeOptionsPanel.class, "BladeOptionsPanel.jLabel1.text")); // NOI18N + + org.openide.awt.Mnemonics.setLocalizedText(auto_tag_completion, org.openide.util.NbBundle.getMessage(BladeOptionsPanel.class, "BladeOptionsPanel.auto_tag_completion.text")); // NOI18N + + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); + this.setLayout(layout); + layout.setHorizontalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addContainerGap() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(jLabel1) + .addComponent(auto_tag_completion)) + .addContainerGap(127, Short.MAX_VALUE)) + ); + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addContainerGap() + .addComponent(jLabel1) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(auto_tag_completion) + .addContainerGap(252, Short.MAX_VALUE)) + ); + }// //GEN-END:initComponents + + void load() { + this.auto_tag_completion.setSelected(GeneralPreferencesUtils.isAutoTagCompletionEnabled()); + } + + void store() { + ModulePreferences.setPrefBoolean(GeneralPreferencesUtils.ENABLE_AUTO_TAG_COMPLETION, this.auto_tag_completion.isSelected()); + } + + boolean valid() { + return true; + } + + boolean changed() { + return false; + } + + + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JCheckBox auto_tag_completion; + private javax.swing.JLabel jLabel1; + // End of variables declaration//GEN-END:variables +} diff --git a/php/php.blade/src/org/netbeans/modules/php/blade/ui/options/BladeOptionsPanelController.java b/php/php.blade/src/org/netbeans/modules/php/blade/ui/options/BladeOptionsPanelController.java new file mode 100644 index 000000000000..6c8c96cdf73d --- /dev/null +++ b/php/php.blade/src/org/netbeans/modules/php/blade/ui/options/BladeOptionsPanelController.java @@ -0,0 +1,94 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.php.blade.ui.options; + +import java.beans.PropertyChangeListener; +import java.beans.PropertyChangeSupport; +import javax.swing.JComponent; +import org.netbeans.modules.php.api.util.UiUtils; +import org.netbeans.spi.options.OptionsPanelController; +import org.openide.util.HelpCtx; +import org.openide.util.Lookup; + +@UiUtils.PhpOptionsPanelRegistration( + id = BladeOptionsPanelController.ID, + displayName = "Blade", // NOI18N + position = 2150 +) +public class BladeOptionsPanelController extends OptionsPanelController { + + public static final String ID = "Blade"; // NOI18N + public static final String CONTEXT_HELP_ID = "org.netbeans.modules.php.blade.editor.Options"; //NOI18N + public static final String OPTIONS_SUBPATH = UiUtils.FRAMEWORKS_AND_TOOLS_SUB_PATH + "/" + ID; // NOI18N + + private BladeOptionsPanel panel; + private final PropertyChangeSupport pcs = new PropertyChangeSupport(this); + + @Override + public void update() { + getPanel().load(); + } + + @Override + public void applyChanges() { + getPanel().store(); + } + + @Override + public void cancel() { + //no action + } + + @Override + public boolean isValid() { + return getPanel().valid(); + } + + @Override + public boolean isChanged() { + return getPanel().changed(); + } + + @Override + public HelpCtx getHelpCtx() { + return new HelpCtx(CONTEXT_HELP_ID); //NOI18N + } + + @Override + public JComponent getComponent(Lookup masterLookup) { + return getPanel(); + } + + @Override + public void addPropertyChangeListener(PropertyChangeListener l) { + pcs.addPropertyChangeListener(l); + } + + @Override + public void removePropertyChangeListener(PropertyChangeListener l) { + pcs.removePropertyChangeListener(l); + } + + private BladeOptionsPanel getPanel() { + if (panel == null) { + panel = new BladeOptionsPanel(); + } + return panel; + } +} diff --git a/php/php.blade/src/org/netbeans/modules/php/blade/ui/options/Bundle.properties b/php/php.blade/src/org/netbeans/modules/php/blade/ui/options/Bundle.properties new file mode 100644 index 000000000000..0094b70d270d --- /dev/null +++ b/php/php.blade/src/org/netbeans/modules/php/blade/ui/options/Bundle.properties @@ -0,0 +1,18 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +BladeOptionsPanel.jLabel1.text=Blade +BladeOptionsPanel.auto_tag_completion.text=Auto tag completion ("{{ }}", "{!! !!}", "{{-- --}}") diff --git a/php/php.blade/test/unit/data/goldenfiles/org/netbeans/modules/php/blade/editor/lexer/BladeLexerTest/content_tag.pass b/php/php.blade/test/unit/data/goldenfiles/org/netbeans/modules/php/blade/editor/lexer/BladeLexerTest/content_tag.pass new file mode 100644 index 000000000000..39b810172f0f --- /dev/null +++ b/php/php.blade/test/unit/data/goldenfiles/org/netbeans/modules/php/blade/editor/lexer/BladeLexerTest/content_tag.pass @@ -0,0 +1,5 @@ +Token #0 HTML [Hello, ] +Token #1 BLADE_ECHO_DELIMITOR [{{] +Token #2 PHP_BLADE_ECHO_EXPR [ $name ] +Token #3 BLADE_ECHO_DELIMITOR [}}] +Token #4 HTML [.\n] diff --git a/php/php.blade/test/unit/data/goldenfiles/org/netbeans/modules/php/blade/editor/lexer/BladeLexerTest/content_tag_tertiary_expr.pass b/php/php.blade/test/unit/data/goldenfiles/org/netbeans/modules/php/blade/editor/lexer/BladeLexerTest/content_tag_tertiary_expr.pass new file mode 100644 index 000000000000..4b2717c21e0c --- /dev/null +++ b/php/php.blade/test/unit/data/goldenfiles/org/netbeans/modules/php/blade/editor/lexer/BladeLexerTest/content_tag_tertiary_expr.pass @@ -0,0 +1,5 @@ +Token #0 HTML [
\n ] +Token #1 BLADE_ECHO_DELIMITOR [{{] +Token #2 PHP_BLADE_ECHO_EXPR [ $isTrue ? 'x' : 'z' ] +Token #3 BLADE_ECHO_DELIMITOR [}}] +Token #4 HTML [\n
\n] diff --git a/php/php.blade/test/unit/data/goldenfiles/org/netbeans/modules/php/blade/editor/lexer/BladeLexerTest/css_at_rules.pass b/php/php.blade/test/unit/data/goldenfiles/org/netbeans/modules/php/blade/editor/lexer/BladeLexerTest/css_at_rules.pass new file mode 100644 index 000000000000..769fda668f05 --- /dev/null +++ b/php/php.blade/test/unit/data/goldenfiles/org/netbeans/modules/php/blade/editor/lexer/BladeLexerTest/css_at_rules.pass @@ -0,0 +1,5 @@ +Token #0 HTML [\n] diff --git a/php/php.blade/test/unit/data/goldenfiles/org/netbeans/modules/php/blade/editor/lexer/BladeLexerTest/escaped_directive.pass b/php/php.blade/test/unit/data/goldenfiles/org/netbeans/modules/php/blade/editor/lexer/BladeLexerTest/escaped_directive.pass new file mode 100644 index 000000000000..c913c061807b --- /dev/null +++ b/php/php.blade/test/unit/data/goldenfiles/org/netbeans/modules/php/blade/editor/lexer/BladeLexerTest/escaped_directive.pass @@ -0,0 +1 @@ +Token #0 HTML [
\n @@escapedDirective\n
\n] diff --git a/php/php.blade/test/unit/data/goldenfiles/org/netbeans/modules/php/blade/editor/lexer/BladeLexerTest/escaped_tag.pass b/php/php.blade/test/unit/data/goldenfiles/org/netbeans/modules/php/blade/editor/lexer/BladeLexerTest/escaped_tag.pass new file mode 100644 index 000000000000..30a35249205c --- /dev/null +++ b/php/php.blade/test/unit/data/goldenfiles/org/netbeans/modules/php/blade/editor/lexer/BladeLexerTest/escaped_tag.pass @@ -0,0 +1 @@ +Token #0 HTML [

Laravel

\n \nHello, @{{ name }}.\n] diff --git a/php/php.blade/test/unit/data/goldenfiles/org/netbeans/modules/php/blade/editor/lexer/BladeLexerTest/html_javascript_01.pass b/php/php.blade/test/unit/data/goldenfiles/org/netbeans/modules/php/blade/editor/lexer/BladeLexerTest/html_javascript_01.pass new file mode 100644 index 000000000000..2eb0f1cecc6a --- /dev/null +++ b/php/php.blade/test/unit/data/goldenfiles/org/netbeans/modules/php/blade/editor/lexer/BladeLexerTest/html_javascript_01.pass @@ -0,0 +1,8 @@ +Token #0 HTML [\n] diff --git a/php/php.blade/test/unit/data/goldenfiles/org/netbeans/modules/php/blade/editor/lexer/BladeLexerTest/include_01.pass b/php/php.blade/test/unit/data/goldenfiles/org/netbeans/modules/php/blade/editor/lexer/BladeLexerTest/include_01.pass new file mode 100644 index 000000000000..ba42c9bdc214 --- /dev/null +++ b/php/php.blade/test/unit/data/goldenfiles/org/netbeans/modules/php/blade/editor/lexer/BladeLexerTest/include_01.pass @@ -0,0 +1,5 @@ +Token #0 BLADE_DIRECTIVE [@include] +Token #1 BLADE_PAREN [(] +Token #2 PHP_BLADE_EXPRESSION ['path', ['key' => 'value']] +Token #3 BLADE_PAREN [)] +Token #4 HTML [\n] diff --git a/php/php.blade/test/unit/data/goldenfiles/org/netbeans/modules/php/blade/editor/lexer/BladeLexerTest/raw_tag.pass b/php/php.blade/test/unit/data/goldenfiles/org/netbeans/modules/php/blade/editor/lexer/BladeLexerTest/raw_tag.pass new file mode 100644 index 000000000000..71ef32ac7c0e --- /dev/null +++ b/php/php.blade/test/unit/data/goldenfiles/org/netbeans/modules/php/blade/editor/lexer/BladeLexerTest/raw_tag.pass @@ -0,0 +1,5 @@ +Token #0 HTML [Hello, ] +Token #1 BLADE_ECHO_DELIMITOR [{!!] +Token #2 PHP_BLADE_ECHO_EXPR [ $name ] +Token #3 BLADE_ECHO_DELIMITOR [!!}] +Token #4 HTML [.\n] diff --git a/php/php.blade/test/unit/data/goldenfiles/org/netbeans/modules/php/blade/syntax/antlr4/v10/AntlrColoringLexerTest/lexer/blade/content_tag.pass b/php/php.blade/test/unit/data/goldenfiles/org/netbeans/modules/php/blade/syntax/antlr4/v10/AntlrColoringLexerTest/lexer/blade/content_tag.pass new file mode 100644 index 000000000000..7879af7ecdb0 --- /dev/null +++ b/php/php.blade/test/unit/data/goldenfiles/org/netbeans/modules/php/blade/syntax/antlr4/v10/AntlrColoringLexerTest/lexer/blade/content_tag.pass @@ -0,0 +1,9 @@ +Token #8 HTML [[@0,0:4='Hello',<8>,1:0]] +Token #8 HTML [[@1,5:5=',',<8>,1:5]] +Token #8 HTML [[@2,6:6=' ',<8>,1:6]] +Token #7 CONTENT_TAG [[@3,7:8='{{',<7>,1:7]] +Token #4 BLADE_PHP_ECHO_EXPR [[@4,9:15=' $name ',<4>,1:9]] +Token #7 CONTENT_TAG [[@5,16:17='}}',<7>,1:16]] +Token #8 HTML [[@6,18:18='.',<8>,1:18]] +Token #8 HTML [[@7,19:19='\n',<8>,1:19]] +Token #-1 EOF [[@8,20:19='',<-1>,2:0]] diff --git a/php/php.blade/test/unit/data/goldenfiles/org/netbeans/modules/php/blade/syntax/antlr4/v10/AntlrColoringLexerTest/lexer/blade/escaped_tag.pass b/php/php.blade/test/unit/data/goldenfiles/org/netbeans/modules/php/blade/syntax/antlr4/v10/AntlrColoringLexerTest/lexer/blade/escaped_tag.pass new file mode 100644 index 000000000000..c22cc486ba41 --- /dev/null +++ b/php/php.blade/test/unit/data/goldenfiles/org/netbeans/modules/php/blade/syntax/antlr4/v10/AntlrColoringLexerTest/lexer/blade/escaped_tag.pass @@ -0,0 +1,17 @@ +Token #8 HTML [[@0,0:0='<',<8>,1:0]] +Token #8 HTML [[@1,1:2='h1',<8>,1:1]] +Token #8 HTML [[@2,3:3='>',<8>,1:3]] +Token #8 HTML [[@3,4:10='Laravel',<8>,1:4]] +Token #8 HTML [[@4,11:18='\n \n',<8>,1:11]] +Token #8 HTML [[@5,19:23='Hello',<8>,3:0]] +Token #8 HTML [[@6,24:24=',',<8>,3:5]] +Token #8 HTML [[@7,25:25=' ',<8>,3:6]] +Token #8 HTML [[@8,26:28='@{{',<8>,3:7]] +Token #8 HTML [[@9,29:29=' ',<8>,3:10]] +Token #8 HTML [[@10,30:33='name',<8>,3:11]] +Token #8 HTML [[@11,34:34=' ',<8>,3:15]] +Token #8 HTML [[@12,35:35='}',<8>,3:16]] +Token #8 HTML [[@13,36:36='}',<8>,3:17]] +Token #8 HTML [[@14,37:37='.',<8>,3:18]] +Token #8 HTML [[@15,38:38='\n',<8>,3:19]] +Token #-1 EOF [[@16,39:38='',<-1>,4:0]] diff --git a/php/php.blade/test/unit/data/goldenfiles/org/netbeans/modules/php/blade/syntax/antlr4/v10/AntlrColoringLexerTest/lexer/blade/raw_tag.pass b/php/php.blade/test/unit/data/goldenfiles/org/netbeans/modules/php/blade/syntax/antlr4/v10/AntlrColoringLexerTest/lexer/blade/raw_tag.pass new file mode 100644 index 000000000000..c90b47dcdb2d --- /dev/null +++ b/php/php.blade/test/unit/data/goldenfiles/org/netbeans/modules/php/blade/syntax/antlr4/v10/AntlrColoringLexerTest/lexer/blade/raw_tag.pass @@ -0,0 +1,9 @@ +Token #8 HTML [[@0,0:4='Hello',<8>,1:0]] +Token #8 HTML [[@1,5:5=',',<8>,1:5]] +Token #8 HTML [[@2,6:6=' ',<8>,1:6]] +Token #6 RAW_TAG [[@3,7:9='{!!',<6>,1:7]] +Token #4 BLADE_PHP_ECHO_EXPR [[@4,10:16=' $name ',<4>,1:10]] +Token #6 RAW_TAG [[@5,17:19='!!}',<6>,1:17]] +Token #8 HTML [[@6,20:20='.',<8>,1:20]] +Token #8 HTML [[@7,21:21='\n',<8>,1:21]] +Token #-1 EOF [[@8,22:21='',<-1>,2:0]] diff --git a/php/php.blade/test/unit/data/testfiles/braces/testClosedIfDirective_01.testClosedIfDirective_01.braces b/php/php.blade/test/unit/data/testfiles/braces/testClosedIfDirective_01.testClosedIfDirective_01.braces new file mode 100644 index 000000000000..7834820e2ca9 --- /dev/null +++ b/php/php.blade/test/unit/data/testfiles/braces/testClosedIfDirective_01.testClosedIfDirective_01.braces @@ -0,0 +1,5 @@ + +*@if*($x) +text +*@end^if* + \ No newline at end of file diff --git a/php/php.blade/test/unit/data/testfiles/braces/testClosedSectionDirective_01.testClosedSectionDirective_01.braces b/php/php.blade/test/unit/data/testfiles/braces/testClosedSectionDirective_01.testClosedSectionDirective_01.braces new file mode 100644 index 000000000000..5cd7ccba213e --- /dev/null +++ b/php/php.blade/test/unit/data/testfiles/braces/testClosedSectionDirective_01.testClosedSectionDirective_01.braces @@ -0,0 +1,5 @@ + +*@sec^tion*($x) +text +*@endsection* + \ No newline at end of file diff --git a/php/php.blade/test/unit/data/testfiles/braces/testUnclosedSectionDirective_01.testUnclosedSectionDirective_01.braces b/php/php.blade/test/unit/data/testfiles/braces/testUnclosedSectionDirective_01.testUnclosedSectionDirective_01.braces new file mode 100644 index 000000000000..fc61ba33fed6 --- /dev/null +++ b/php/php.blade/test/unit/data/testfiles/braces/testUnclosedSectionDirective_01.testUnclosedSectionDirective_01.braces @@ -0,0 +1,5 @@ + +*@sec^tion*($x) +text +@endsec + \ No newline at end of file diff --git a/php/php.blade/test/unit/data/testfiles/completion/testCompletion_01.blade.php b/php/php.blade/test/unit/data/testfiles/completion/testCompletion_01.blade.php new file mode 100644 index 000000000000..59c227c5c811 --- /dev/null +++ b/php/php.blade/test/unit/data/testfiles/completion/testCompletion_01.blade.php @@ -0,0 +1 @@ +@ diff --git a/php/php.blade/test/unit/data/testfiles/completion/testCompletion_01.blade.php.testCompletion_01.completion b/php/php.blade/test/unit/data/testfiles/completion/testCompletion_01.blade.php.testCompletion_01.completion new file mode 100644 index 000000000000..5e9b6bc8d082 --- /dev/null +++ b/php/php.blade/test/unit/data/testfiles/completion/testCompletion_01.blade.php.testCompletion_01.completion @@ -0,0 +1,101 @@ +Code completion result for source line: +@| +(QueryType=COMPLETION, prefixSearch=true, caseSensitive=true) +CONSTRUCTO @append +CONSTRUCTO @break +CONSTRUCTO @continue +CONSTRUCTO @csrf +CONSTRUCTO @default +CONSTRUCTO @else +CONSTRUCTO @empty v5.4 +CONSTRUCTO @endPushIf +CONSTRUCTO @endfor +CONSTRUCTO @endforeach@endforelse
@endfragment@endif
+CONSTRUCTO @endonce +CONSTRUCTO @endprepend@endpush
+CONSTRUCTO @endsection@once +CONSTRUCTO @overwrite +CONSTRUCTO @php +CONSTRUCTO @stop +CONSTRUCTO @verbatim +CONSTRUCTO @auth() +CONSTRUCTO @auth() ... @endauth +CONSTRUCTO @bool() v11 +CONSTRUCTO @can() v5.1 +CONSTRUCTO @can() ... @endcan v5.1 +CONSTRUCTO @canany() v5.8 +CONSTRUCTO @canany() ... @endcanany v5.8 +CONSTRUCTO @cannot() v5.3 +CONSTRUCTO @cannot() ... @endcannot v5.3 +CONSTRUCTO @case() +CONSTRUCTO @checked() v9 +CONSTRUCTO @class() v8 +CONSTRUCTO @dd() +CONSTRUCTO @disabled() v9 +CONSTRUCTO @dump() +CONSTRUCTO @each() +CONSTRUCTO @elseif() +CONSTRUCTO @empty() v5.4 +CONSTRUCTO @empty() ... @endempty v5.4 +CONSTRUCTO @env() +CONSTRUCTO @env() ... @endenv +CONSTRUCTO @error() +CONSTRUCTO @error() ... @enderror +CONSTRUCTO @extends() +CONSTRUCTO @for() +CONSTRUCTO @for() ... @endfor +CONSTRUCTO @foreach() +CONSTRUCTO @foreach() ... @endforeach +CONSTRUCTO @forelse() +CONSTRUCTO @forelse() ... @endforelse +CONSTRUCTO @fragment() +CONSTRUCTO @fragment() ... @endfragment +CONSTRUCTO @guest() +CONSTRUCTO @guest() ... @endguest +CONSTRUCTO @hasSection() +CONSTRUCTO @hasSection() ... @endif +CONSTRUCTO @if() +CONSTRUCTO @if() ... @endif +CONSTRUCTO @include() +CONSTRUCTO @includeFirst() +CONSTRUCTO @includeIf() +CONSTRUCTO @includeUnless() +CONSTRUCTO @includeWhen() +CONSTRUCTO @inject() +CONSTRUCTO @isset() +CONSTRUCTO @isset() ... @endisset +CONSTRUCTO @json() +CONSTRUCTO @lang() +CONSTRUCTO @method() +CONSTRUCTO @php ... @endphp +CONSTRUCTO @prepend() +CONSTRUCTO @prepend() ... @endprepend +CONSTRUCTO @props() v7.4 +CONSTRUCTO @push() +CONSTRUCTO @push() ... @endpush +CONSTRUCTO @pushIf() +CONSTRUCTO @pushIf() ... @endPushIf +CONSTRUCTO @readony() v9 +CONSTRUCTO @required() v9 +CONSTRUCTO @section() +CONSTRUCTO @section() ... @endsection +CONSTRUCTO @sectionMissing() +CONSTRUCTO @sectionMissing() ... @endif +CONSTRUCTO @session() v10 +CONSTRUCTO @session() ... @endsession v10 +CONSTRUCTO @stack() +CONSTRUCTO @style() v9 +CONSTRUCTO @switch() +CONSTRUCTO @switch() ... @endswitch +CONSTRUCTO @unless() +CONSTRUCTO @unless() ... @endunless +CONSTRUCTO @use() +CONSTRUCTO @verbatim ... @endverbatim +CONSTRUCTO @vite() v11 +CONSTRUCTO @when() v11 +CONSTRUCTO @yield() diff --git a/php/php.blade/test/unit/data/testfiles/completion/testCompletion_loop_endtag_01.blade.php b/php/php.blade/test/unit/data/testfiles/completion/testCompletion_loop_endtag_01.blade.php new file mode 100644 index 000000000000..8cae35352261 --- /dev/null +++ b/php/php.blade/test/unit/data/testfiles/completion/testCompletion_loop_endtag_01.blade.php @@ -0,0 +1,3 @@ +@foreach($array as $key => $item) + +@endforeach \ No newline at end of file diff --git a/php/php.blade/test/unit/data/testfiles/completion/testCompletion_loop_endtag_01.blade.php.testCompletion_loop_endtag_01.completion b/php/php.blade/test/unit/data/testfiles/completion/testCompletion_loop_endtag_01.blade.php.testCompletion_loop_endtag_01.completion new file mode 100644 index 000000000000..6b2aed7586db --- /dev/null +++ b/php/php.blade/test/unit/data/testfiles/completion/testCompletion_loop_endtag_01.blade.php.testCompletion_loop_endtag_01.completion @@ -0,0 +1,4 @@ +Code completion result for source line: +@endfor|each +(QueryType=COMPLETION, prefixSearch=true, caseSensitive=true) +CONSTRUCTO @endforeach + {{ $variable }} + +@endsection diff --git a/php/php.blade/test/unit/data/testfiles/embedding/html_embedding_01.blade.php.testHtmlEmbedding_01.embedding b/php/php.blade/test/unit/data/testfiles/embedding/html_embedding_01.blade.php.testHtmlEmbedding_01.embedding new file mode 100644 index 000000000000..c25eb44b044c --- /dev/null +++ b/php/php.blade/test/unit/data/testfiles/embedding/html_embedding_01.blade.php.testHtmlEmbedding_01.embedding @@ -0,0 +1,5 @@ + +
+ +
+ diff --git a/php/php.blade/test/unit/data/testfiles/embedding/php_inline_embedding_01.blade.php b/php/php.blade/test/unit/data/testfiles/embedding/php_inline_embedding_01.blade.php new file mode 100644 index 000000000000..e3d154236e27 --- /dev/null +++ b/php/php.blade/test/unit/data/testfiles/embedding/php_inline_embedding_01.blade.php @@ -0,0 +1,5 @@ +{{ $x }} + + +
\ No newline at end of file diff --git a/php/php.blade/test/unit/data/testfiles/embedding/php_inline_embedding_02.blade.php.testPhpInlineEmbedding_02.embedding b/php/php.blade/test/unit/data/testfiles/embedding/php_inline_embedding_02.blade.php.testPhpInlineEmbedding_02.embedding new file mode 100644 index 000000000000..cea1fe38161c --- /dev/null +++ b/php/php.blade/test/unit/data/testfiles/embedding/php_inline_embedding_02.blade.php.testPhpInlineEmbedding_02.embedding @@ -0,0 +1,4 @@ +@@@@@@@@@@@@ \ No newline at end of file diff --git a/php/php.blade/test/unit/data/testfiles/format/directive_blocks_01.blade.php b/php/php.blade/test/unit/data/testfiles/format/directive_blocks_01.blade.php new file mode 100644 index 000000000000..c82e64bfcfe2 --- /dev/null +++ b/php/php.blade/test/unit/data/testfiles/format/directive_blocks_01.blade.php @@ -0,0 +1,4 @@ +@if($x) +@section($y) +@endsection +@endif \ No newline at end of file diff --git a/php/php.blade/test/unit/data/testfiles/format/directive_blocks_01.blade.php.formatted b/php/php.blade/test/unit/data/testfiles/format/directive_blocks_01.blade.php.formatted new file mode 100644 index 000000000000..0cb15e640471 --- /dev/null +++ b/php/php.blade/test/unit/data/testfiles/format/directive_blocks_01.blade.php.formatted @@ -0,0 +1,4 @@ +@if($x) + @section($y) + @endsection +@endif \ No newline at end of file diff --git a/php/php.blade/test/unit/data/testfiles/format/directive_blocks_02.blade.php b/php/php.blade/test/unit/data/testfiles/format/directive_blocks_02.blade.php new file mode 100644 index 000000000000..e13a820af8f2 --- /dev/null +++ b/php/php.blade/test/unit/data/testfiles/format/directive_blocks_02.blade.php @@ -0,0 +1,6 @@ +@if($x) +@section($y) +@if($x) +@endif +@endsection +@endif \ No newline at end of file diff --git a/php/php.blade/test/unit/data/testfiles/format/directive_blocks_02.blade.php.formatted b/php/php.blade/test/unit/data/testfiles/format/directive_blocks_02.blade.php.formatted new file mode 100644 index 000000000000..2d8b0adeb035 --- /dev/null +++ b/php/php.blade/test/unit/data/testfiles/format/directive_blocks_02.blade.php.formatted @@ -0,0 +1,6 @@ +@if($x) + @section($y) + @if($x) + @endif + @endsection +@endif \ No newline at end of file diff --git a/php/php.blade/test/unit/data/testfiles/format/if_block_01.blade.php b/php/php.blade/test/unit/data/testfiles/format/if_block_01.blade.php new file mode 100644 index 000000000000..ea4a045ce5c3 --- /dev/null +++ b/php/php.blade/test/unit/data/testfiles/format/if_block_01.blade.php @@ -0,0 +1,4 @@ +@if($x) +@if($y) +@endif +@endif \ No newline at end of file diff --git a/php/php.blade/test/unit/data/testfiles/format/if_block_01.blade.php.formatted b/php/php.blade/test/unit/data/testfiles/format/if_block_01.blade.php.formatted new file mode 100644 index 000000000000..c2e578b75a3f --- /dev/null +++ b/php/php.blade/test/unit/data/testfiles/format/if_block_01.blade.php.formatted @@ -0,0 +1,4 @@ +@if($x) + @if($y) + @endif +@endif \ No newline at end of file diff --git a/php/php.blade/test/unit/data/testfiles/lexer/blade/content_tag.blade.php b/php/php.blade/test/unit/data/testfiles/lexer/blade/content_tag.blade.php new file mode 100644 index 000000000000..3858bd8c876e --- /dev/null +++ b/php/php.blade/test/unit/data/testfiles/lexer/blade/content_tag.blade.php @@ -0,0 +1 @@ +Hello, {{ $name }}. diff --git a/php/php.blade/test/unit/data/testfiles/lexer/blade/content_tag_tertiary_expr.blade.php b/php/php.blade/test/unit/data/testfiles/lexer/blade/content_tag_tertiary_expr.blade.php new file mode 100644 index 000000000000..59cdf28239f7 --- /dev/null +++ b/php/php.blade/test/unit/data/testfiles/lexer/blade/content_tag_tertiary_expr.blade.php @@ -0,0 +1,3 @@ +
+ {{ $isTrue ? 'x' : 'z' }} +
\ No newline at end of file diff --git a/php/php.blade/test/unit/data/testfiles/lexer/blade/css_at_rules.blade.php b/php/php.blade/test/unit/data/testfiles/lexer/blade/css_at_rules.blade.php new file mode 100644 index 000000000000..53b7eccd67fc --- /dev/null +++ b/php/php.blade/test/unit/data/testfiles/lexer/blade/css_at_rules.blade.php @@ -0,0 +1,34 @@ + diff --git a/php/php.blade/test/unit/data/testfiles/lexer/blade/escaped_directive.blade.php b/php/php.blade/test/unit/data/testfiles/lexer/blade/escaped_directive.blade.php new file mode 100644 index 000000000000..3f8b5b71ac71 --- /dev/null +++ b/php/php.blade/test/unit/data/testfiles/lexer/blade/escaped_directive.blade.php @@ -0,0 +1,3 @@ +
+ @@escapedDirective +
diff --git a/php/php.blade/test/unit/data/testfiles/lexer/blade/escaped_tag.blade.php b/php/php.blade/test/unit/data/testfiles/lexer/blade/escaped_tag.blade.php new file mode 100644 index 000000000000..9516e7ea7e3a --- /dev/null +++ b/php/php.blade/test/unit/data/testfiles/lexer/blade/escaped_tag.blade.php @@ -0,0 +1,3 @@ +

Laravel

+ +Hello, @{{ name }}. diff --git a/php/php.blade/test/unit/data/testfiles/lexer/blade/html_javascript_01.blade.php b/php/php.blade/test/unit/data/testfiles/lexer/blade/html_javascript_01.blade.php new file mode 100644 index 000000000000..d195ba0d8de1 --- /dev/null +++ b/php/php.blade/test/unit/data/testfiles/lexer/blade/html_javascript_01.blade.php @@ -0,0 +1,6 @@ + \ No newline at end of file diff --git a/php/php.blade/test/unit/data/testfiles/lexer/blade/include_01.blade.php b/php/php.blade/test/unit/data/testfiles/lexer/blade/include_01.blade.php new file mode 100644 index 000000000000..7b441c67f4b2 --- /dev/null +++ b/php/php.blade/test/unit/data/testfiles/lexer/blade/include_01.blade.php @@ -0,0 +1 @@ +@include('path', ['key' => 'value']) diff --git a/php/php.blade/test/unit/data/testfiles/lexer/blade/performance/perf_test_01.blade.php b/php/php.blade/test/unit/data/testfiles/lexer/blade/performance/perf_test_01.blade.php new file mode 100644 index 000000000000..ee323cf41bc7 --- /dev/null +++ b/php/php.blade/test/unit/data/testfiles/lexer/blade/performance/perf_test_01.blade.php @@ -0,0 +1,66 @@ +@extends('layout.layout') +
+ @if (count($records) === 1) + I have one record! + @elseif (count($records) > 1) + I have multiple records! + @else + I don't have any records! + @endif +
+ +@sectionMissing('navigation') +
+ @include('default-navigation') +
+@endif + +@for ($i = 0; $i < 10; $i++) + The current value is {{ $i }} +@endfor + +@foreach ($users as $user) +

This is user {{ $user->id }}

+@endforeach + +@forelse ($users as $user) +
  • {{ $user->name }}
  • +@empty +

    No users

    +@endforelse + +@while (true) +

    I'm looping forever.

    +@endwhile + +@php + $isActive = false; + $hasError = true; +@endphp + + $isActive, + 'text-gray-500' => ! $isActive, + 'bg-red' => $hasError, +])> + + + +
    + @include('shared.errors') + +
    + +
    +
    + +@once + @push('scripts') + + @endpush +@endonce + +@use('App\Models\Flight', 'FlightModel') diff --git a/php/php.blade/test/unit/data/testfiles/lexer/blade/raw_tag.blade.php b/php/php.blade/test/unit/data/testfiles/lexer/blade/raw_tag.blade.php new file mode 100644 index 000000000000..9f9f75fce5bc --- /dev/null +++ b/php/php.blade/test/unit/data/testfiles/lexer/blade/raw_tag.blade.php @@ -0,0 +1 @@ +Hello, {!! $name !!}. diff --git a/php/php.blade/test/unit/data/testfiles/multiple_block_directives.blade.php.structure b/php/php.blade/test/unit/data/testfiles/multiple_block_directives.blade.php.structure new file mode 100644 index 000000000000..99bffe6e1f53 --- /dev/null +++ b/php/php.blade/test/unit/data/testfiles/multiple_block_directives.blade.php.structure @@ -0,0 +1,5 @@ +@if:CLASS:[]:ESCAPED{@if}: + @section:CLASS:[]:ESCAPED{@section}: + @include:CLASS:[]:ESCAPED{@include}ESCAPED{ }ESCAPED{path1}: +@if:CLASS:[]:ESCAPED{@if}ESCAPED{ }ESCAPED{path2}: + @include:CLASS:[]:ESCAPED{@include}ESCAPED{ }ESCAPED{path2}: diff --git a/php/php.blade/test/unit/data/testfiles/navigator/multiple_block_directives.blade.php b/php/php.blade/test/unit/data/testfiles/navigator/multiple_block_directives.blade.php new file mode 100644 index 000000000000..1a0b2b7fdd11 --- /dev/null +++ b/php/php.blade/test/unit/data/testfiles/navigator/multiple_block_directives.blade.php @@ -0,0 +1,9 @@ +@if($x) + @section($arg) + @include('path1') + @endsection +@endif + +@if($y) + @include('path2') +@endif diff --git a/php/php.blade/test/unit/data/testfiles/parser/errors/wrong_empty_block_syntax_error.blade.php b/php/php.blade/test/unit/data/testfiles/parser/errors/wrong_empty_block_syntax_error.blade.php new file mode 100644 index 000000000000..d9ec3a2e8173 --- /dev/null +++ b/php/php.blade/test/unit/data/testfiles/parser/errors/wrong_empty_block_syntax_error.blade.php @@ -0,0 +1,6 @@ +@empty($x) +
    + @else +
    + @endempty + \ No newline at end of file diff --git a/php/php.blade/test/unit/data/testfiles/parser/errors/wrong_empty_block_syntax_error.blade.php.errors b/php/php.blade/test/unit/data/testfiles/parser/errors/wrong_empty_block_syntax_error.blade.php.errors new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/php/php.blade/test/unit/data/testfiles/parser/errors/wrong_foreach_syntax_error.blade.php b/php/php.blade/test/unit/data/testfiles/parser/errors/wrong_foreach_syntax_error.blade.php new file mode 100644 index 000000000000..d33a26e12f26 --- /dev/null +++ b/php/php.blade/test/unit/data/testfiles/parser/errors/wrong_foreach_syntax_error.blade.php @@ -0,0 +1,15 @@ +@foreach($tests->task as $test) + +@endforeach + +@foreach(App\Models\Task::STATUS as $status) + +@endforeach + +@foreach($array['AS'] as $status) + +@endforeach + +@foreach($array["AS"] as $status) + +@endforeach diff --git a/php/php.blade/test/unit/data/testfiles/parser/errors/wrong_foreach_syntax_error.blade.php.errors b/php/php.blade/test/unit/data/testfiles/parser/errors/wrong_foreach_syntax_error.blade.php.errors new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/php/php.blade/test/unit/data/testfiles/parser/performance/perf_test_01.blade.php b/php/php.blade/test/unit/data/testfiles/parser/performance/perf_test_01.blade.php new file mode 100644 index 000000000000..ee323cf41bc7 --- /dev/null +++ b/php/php.blade/test/unit/data/testfiles/parser/performance/perf_test_01.blade.php @@ -0,0 +1,66 @@ +@extends('layout.layout') +
    + @if (count($records) === 1) + I have one record! + @elseif (count($records) > 1) + I have multiple records! + @else + I don't have any records! + @endif +
    + +@sectionMissing('navigation') +
    + @include('default-navigation') +
    +@endif + +@for ($i = 0; $i < 10; $i++) + The current value is {{ $i }} +@endfor + +@foreach ($users as $user) +

    This is user {{ $user->id }}

    +@endforeach + +@forelse ($users as $user) +
  • {{ $user->name }}
  • +@empty +

    No users

    +@endforelse + +@while (true) +

    I'm looping forever.

    +@endwhile + +@php + $isActive = false; + $hasError = true; +@endphp + + $isActive, + 'text-gray-500' => ! $isActive, + 'bg-red' => $hasError, +])> + + + +
    + @include('shared.errors') + +
    + +
    +
    + +@once + @push('scripts') + + @endpush +@endonce + +@use('App\Models\Flight', 'FlightModel') diff --git a/php/php.blade/test/unit/data/testfiles/parser/performance/perf_test_01.blade.php.errors b/php/php.blade/test/unit/data/testfiles/parser/performance/perf_test_01.blade.php.errors new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/php/php.blade/test/unit/data/testfiles/parser/smoke/test_01.blade.php b/php/php.blade/test/unit/data/testfiles/parser/smoke/test_01.blade.php new file mode 100644 index 000000000000..5eff48815e7c --- /dev/null +++ b/php/php.blade/test/unit/data/testfiles/parser/smoke/test_01.blade.php @@ -0,0 +1,9 @@ +@extends('layout.layout') + +@section('content') +
    +@endsection + +@php +$value = $var['1'] +@endphp diff --git a/php/php.blade/test/unit/data/testfiles/parser/unopend/inline_section_noerror_01.blade.php b/php/php.blade/test/unit/data/testfiles/parser/unopend/inline_section_noerror_01.blade.php new file mode 100644 index 000000000000..c7138cccd4c5 --- /dev/null +++ b/php/php.blade/test/unit/data/testfiles/parser/unopend/inline_section_noerror_01.blade.php @@ -0,0 +1 @@ +@section('x', ['var']) \ No newline at end of file diff --git a/php/php.blade/test/unit/data/testfiles/parser/unopend/inline_section_noerror_01.blade.php.errors b/php/php.blade/test/unit/data/testfiles/parser/unopend/inline_section_noerror_01.blade.php.errors new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/php/php.blade/test/unit/data/testfiles/parser/unopend/unclosed_directive_bracket_error_01.blade.php b/php/php.blade/test/unit/data/testfiles/parser/unopend/unclosed_directive_bracket_error_01.blade.php new file mode 100644 index 000000000000..f3b73db6eb9f --- /dev/null +++ b/php/php.blade/test/unit/data/testfiles/parser/unopend/unclosed_directive_bracket_error_01.blade.php @@ -0,0 +1 @@ +@test([[]) \ No newline at end of file diff --git a/php/php.blade/test/unit/data/testfiles/parser/unopend/unclosed_directive_bracket_error_01.blade.php.errors b/php/php.blade/test/unit/data/testfiles/parser/unopend/unclosed_directive_bracket_error_01.blade.php.errors new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/php/php.blade/test/unit/data/testfiles/parser/unopend/unclosed_section_error_01.blade.php b/php/php.blade/test/unit/data/testfiles/parser/unopend/unclosed_section_error_01.blade.php new file mode 100644 index 000000000000..53be5e00c00a --- /dev/null +++ b/php/php.blade/test/unit/data/testfiles/parser/unopend/unclosed_section_error_01.blade.php @@ -0,0 +1 @@ +@section('x') \ No newline at end of file diff --git a/php/php.blade/test/unit/data/testfiles/parser/unopend/unclosed_section_error_01.blade.php.errors b/php/php.blade/test/unit/data/testfiles/parser/unopend/unclosed_section_error_01.blade.php.errors new file mode 100644 index 000000000000..17dc2a857e17 --- /dev/null +++ b/php/php.blade/test/unit/data/testfiles/parser/unopend/unclosed_section_error_01.blade.php.errors @@ -0,0 +1 @@ +13-13:Inline @section requires second argument diff --git a/php/php.blade/test/unit/data/testfiles/parser/unopend/unopend_set_error_01.blade.php b/php/php.blade/test/unit/data/testfiles/parser/unopend/unopend_set_error_01.blade.php new file mode 100644 index 000000000000..4e4e5c319125 --- /dev/null +++ b/php/php.blade/test/unit/data/testfiles/parser/unopend/unopend_set_error_01.blade.php @@ -0,0 +1,3 @@ +@if($f) +@endif +@endif diff --git a/php/php.blade/test/unit/data/testfiles/parser/unopend/unopend_set_error_01.blade.php.errors b/php/php.blade/test/unit/data/testfiles/parser/unopend/unopend_set_error_01.blade.php.errors new file mode 100644 index 000000000000..89908f02090d --- /dev/null +++ b/php/php.blade/test/unit/data/testfiles/parser/unopend/unopend_set_error_01.blade.php.errors @@ -0,0 +1 @@ +15-15:extraneous input '@endif' expecting {, D_CUSTOM, D_DIRECTIVE, '}}', D_PHP, D_IF, '@else', D_UNLESS, D_ISSET, D_SWITCH, D_EXTENDS, D_INCLUDE, D_INCLUDE_IF, D_INCLUDE_WHEN, D_INCLUDE_UNLESS, D_INCLUDE_FIRST, D_EACH, D_YIELD, D_SECTION, D_HAS_SECTION, D_SECTION_MISSING, '@once', D_STACK, D_PUSH, D_PUSH_IF, D_PUSH_ONCE, D_PREPEND, D_FOREACH, D_FOR, D_FORELSE, D_WHILE, D_EMPTY, '@verbatim', D_SESSION, '@csrf', D_METHOD, D_ERROR, D_AUTH, D_GUEST, D_ENV, '@production', D_CAN, D_CANNOT, D_CANANY, D_FRAGMENT, D_SIMPLE_DIRECTIVE, D_PROPS, D_VITE, '@viteReactRefresh', '@lang', D_INJECT, '@use', '{{', '{!!', '!!}', D_ENDCUSTOM, PHP_INLINE_START, HTML_COMPONENT_OPEN_TAG} diff --git a/php/php.blade/test/unit/src/org/netbeans/modules/php/blade/editor/BladeGoldenFileTestBase.java b/php/php.blade/test/unit/src/org/netbeans/modules/php/blade/editor/BladeGoldenFileTestBase.java new file mode 100644 index 000000000000..793227b8c91e --- /dev/null +++ b/php/php.blade/test/unit/src/org/netbeans/modules/php/blade/editor/BladeGoldenFileTestBase.java @@ -0,0 +1,57 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.php.blade.editor; + +import java.io.File; +import org.openide.filesystems.FileObject; +import org.openide.filesystems.FileUtil; + +/** + * + * @author bogdan + */ +public abstract class BladeGoldenFileTestBase extends BladeTestBase { + + public BladeGoldenFileTestBase(String testName) { + super(testName); + } + + protected abstract String getTestResult(String filename) throws Exception; + + protected void performTest(String filename) throws Exception { + // parse the file + String result = getTestResult(filename); + String fullClassName = this.getClass().getName(); + String goldenFileDir = fullClassName.replace('.', '/'); + // try to find golden file + String goldenFolder = getDataSourceDir().getAbsolutePath() + "/goldenfiles/" + goldenFileDir + "/"; + File goldenFile = new File(goldenFolder + filename + ".pass"); + if (!goldenFile.exists()) { + // if doesn't exist, create it + FileObject goldenFO = touch(goldenFolder, filename + ".pass"); + copyStringToFileObject(goldenFO, result); + } else { + // if exist, compare it. + goldenFile = getGoldenFile(filename + ".pass"); + FileObject resultFO = touch(getWorkDir(), filename + ".result"); + copyStringToFileObject(resultFO, result); + assertFile(FileUtil.toFile(resultFO), goldenFile, getWorkDir()); + } + } +} diff --git a/php/php.blade/test/unit/src/org/netbeans/modules/php/blade/editor/BladeTestBase.java b/php/php.blade/test/unit/src/org/netbeans/modules/php/blade/editor/BladeTestBase.java new file mode 100644 index 000000000000..eb069a45f111 --- /dev/null +++ b/php/php.blade/test/unit/src/org/netbeans/modules/php/blade/editor/BladeTestBase.java @@ -0,0 +1,43 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.php.blade.editor; + +import org.netbeans.modules.csl.api.test.CslTestBase; +import org.netbeans.modules.csl.spi.DefaultLanguageConfig; + +/** + * + * @author bogdan + */ +public abstract class BladeTestBase extends CslTestBase { + + public BladeTestBase(String testName) { + super(testName); + } + + @Override + protected DefaultLanguageConfig getPreferredLanguage() { + return new BladeLanguage(); + } + + @Override + protected String getPreferredMimeType() { + return BladeLanguage.MIME_TYPE; + } +} diff --git a/php/php.blade/test/unit/src/org/netbeans/modules/php/blade/editor/BladeUtils.java b/php/php.blade/test/unit/src/org/netbeans/modules/php/blade/editor/BladeUtils.java new file mode 100644 index 000000000000..b11664a72af2 --- /dev/null +++ b/php/php.blade/test/unit/src/org/netbeans/modules/php/blade/editor/BladeUtils.java @@ -0,0 +1,85 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.php.blade.editor; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.InputStreamReader; +import org.antlr.v4.runtime.*; +import org.netbeans.modules.php.blade.syntax.antlr4.formatter.BladeAntlrFormatterLexer; +import org.netbeans.modules.php.blade.syntax.antlr4.v10.BladeAntlrColoringLexer; +import org.netbeans.modules.php.blade.syntax.antlr4.v10.BladeAntlrLexer; + +/** + * + * @author bhaidu + */ +public final class BladeUtils { + + private BladeUtils() { + + } + + public static String getFileContent(File file) throws Exception { + StringBuffer sb = new StringBuffer(); + String lineSep = "\n"; + BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(file), "UTF-8")); + String line = br.readLine(); + while (line != null) { + sb.append(line); + sb.append(lineSep); + line = br.readLine(); + } + br.close(); + return sb.toString(); + } + + public static CommonTokenStream getTokenStream(String content) { + CharStream stream = CharStreams.fromString(content); + BladeAntlrLexer lexer = new BladeAntlrLexer(stream); + CommonTokenStream tokens = new CommonTokenStream(lexer); + tokens.fill(); + return tokens; + } + + public static CommonTokenStream getColoringTokenStream(String content) { + CharStream stream = CharStreams.fromString(content); + BladeAntlrColoringLexer lexer = new BladeAntlrColoringLexer(stream); + CommonTokenStream tokens = new CommonTokenStream(lexer); + tokens.fill(); + return tokens; + } + + public static CommonTokenStream getFormatTokenStream(String content) { + CharStream stream = CharStreams.fromString(content); + BladeAntlrFormatterLexer lexer = new BladeAntlrFormatterLexer(stream); + CommonTokenStream tokens = new CommonTokenStream(lexer); + tokens.fill(); + return tokens; + } + + public static String replaceLinesAndTabs(String input) { + String escapedString = input; + escapedString = escapedString.replaceAll("\n", "\\\\n"); + escapedString = escapedString.replaceAll("\r", "\\\\r"); + escapedString = escapedString.replaceAll("\t", "\\\\t"); + return escapedString; + } +} diff --git a/php/php.blade/test/unit/src/org/netbeans/modules/php/blade/editor/braces/BladeBracesMatcherTest.java b/php/php.blade/test/unit/src/org/netbeans/modules/php/blade/editor/braces/BladeBracesMatcherTest.java new file mode 100644 index 000000000000..7f5893e688ff --- /dev/null +++ b/php/php.blade/test/unit/src/org/netbeans/modules/php/blade/editor/braces/BladeBracesMatcherTest.java @@ -0,0 +1,112 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.php.blade.editor.braces; + +import java.util.Arrays; +import java.util.Collections; +import org.netbeans.api.editor.mimelookup.MimeLookup; +import org.netbeans.editor.BaseDocument; +import org.netbeans.modules.editor.bracesmatching.api.BracesMatchingTestUtils; +import org.netbeans.modules.php.blade.editor.BladeTestBase; +import org.netbeans.spi.editor.bracesmatching.BracesMatcher; +import org.netbeans.spi.editor.bracesmatching.BracesMatcherFactory; +import org.netbeans.spi.editor.bracesmatching.MatcherContext; + +/** + * + * + */ +public class BladeBracesMatcherTest extends BladeTestBase { + + public BladeBracesMatcherTest(String testName) { + super(testName); + } + + public void testClosedIfDirective_01() throws Exception { + testMatches("\n" + + "@if($x)\n" + + "text\n" + + "@end^if\n" + + ""); + } + + public void testClosedSectionDirective_01() throws Exception { + testMatches("\n" + + "@sec^tion($x)\n" + + "text\n" + + "@endsection\n" + + ""); + } + + public void testUnclosedSectionDirective_01() throws Exception { + testMatches("\n" + + "@sec^tion($x)\n" + + "text\n" + + "@endsec\n" + + ""); + } + + private void testMatches(String original) throws Exception { + BracesMatcherFactory factory = MimeLookup.getLookup(getPreferredMimeType()).lookup(BracesMatcherFactory.class); + int caretPosition = original.indexOf('^'); + assert caretPosition != -1; + original = original.substring(0, caretPosition) + original.substring(caretPosition + 1); + + BaseDocument doc = getDocument(original); + + MatcherContext context = BracesMatchingTestUtils.createMatcherContext(doc, caretPosition, false, 1); + BracesMatcher matcher = factory.createMatcher(context); + int[] origin = null, matches = null; + try { + origin = matcher.findOrigin(); + matches = matcher.findMatches(); + } catch (InterruptedException ex) { + } + + assertNotNull("Did not find origin", origin); + assertEquals("Wrong origin length", 2, origin.length); + + int matchesLength = 0; + if (matches != null) { + matchesLength = matches.length; + } + int[] boundaries = new int[origin.length + matchesLength]; + System.arraycopy(origin, 0, boundaries, 0, origin.length); + if (matchesLength != 0) { + System.arraycopy(matches, 0, boundaries, origin.length, matches.length); + } + + Integer[] boundariesIntegers = new Integer[boundaries.length]; + for (int i = 0; i < boundaries.length; i++) { + boundariesIntegers[i] = boundaries[i]; + } + Arrays.sort(boundariesIntegers, Collections.reverseOrder()); + String expected = original; + boolean caretInserted = false; + for (int i : boundariesIntegers) { + if (i <= caretPosition && !caretInserted) { + expected = expected.substring(0, caretPosition) + "^" + expected.substring(caretPosition); + caretInserted = true; + } + expected = expected.substring(0, i) + "*" + expected.substring(i); + } + assertDescriptionMatches("testfiles/braces/" + getName(), expected, true, ".braces", false); + } + +} diff --git a/php/php.blade/test/unit/src/org/netbeans/modules/php/blade/editor/completion/BladeCompletionTest.java b/php/php.blade/test/unit/src/org/netbeans/modules/php/blade/editor/completion/BladeCompletionTest.java new file mode 100644 index 000000000000..ed9d9048a18f --- /dev/null +++ b/php/php.blade/test/unit/src/org/netbeans/modules/php/blade/editor/completion/BladeCompletionTest.java @@ -0,0 +1,40 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.php.blade.editor.completion; + +import org.netbeans.modules.php.blade.editor.BladeTestBase; + +/** + * + * @author bogdan + */ +public class BladeCompletionTest extends BladeTestBase { + + public BladeCompletionTest(String testName) { + super(testName); + } + + public void testCompletion_01() throws Exception { + checkCompletion("testfiles/completion/testCompletion_01.blade.php", "@^", false); + } + + public void testCompletion_loop_endtag_01() throws Exception { + checkCompletion("testfiles/completion/testCompletion_loop_endtag_01.blade.php", "@endfor^", false); + } +} diff --git a/php/php.blade/test/unit/src/org/netbeans/modules/php/blade/editor/embedding/BladeHtmlEmbeddingProviderTest.java b/php/php.blade/test/unit/src/org/netbeans/modules/php/blade/editor/embedding/BladeHtmlEmbeddingProviderTest.java new file mode 100644 index 000000000000..83cd72aa41c1 --- /dev/null +++ b/php/php.blade/test/unit/src/org/netbeans/modules/php/blade/editor/embedding/BladeHtmlEmbeddingProviderTest.java @@ -0,0 +1,61 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.php.blade.editor.embedding; + +import java.util.List; +import org.netbeans.modules.parsing.api.Embedding; +import org.netbeans.modules.parsing.api.Source; +import org.netbeans.modules.parsing.spi.EmbeddingProvider; +import org.netbeans.modules.php.blade.editor.BladeTestBase; + +/** + * + * @author Ondrej Brejla + */ +public class BladeHtmlEmbeddingProviderTest extends BladeTestBase { + + public BladeHtmlEmbeddingProviderTest(String testName) { + super(testName); + } + + private void checkHtmlEmbedding(final String relFilePath) throws Exception { + checkEmbedding(relFilePath, new BladeHtmlEmbeddingProvider()); + } + + private void checkEmbedding(final String relFilePath, EmbeddingProvider embeddingProvider) throws Exception { + assertNotNull(embeddingProvider); + String testedFilePath = "testfiles/embedding/" + relFilePath + ".blade.php"; + Source testSource = getTestSource(getTestFile(testedFilePath)); + List embeddings = embeddingProvider.getEmbeddings(testSource.createSnapshot()); + assertDescriptionMatches(testedFilePath, serializableEmbeddings(embeddings), true, ".embedding"); + } + + private String serializableEmbeddings(List embeddings) { + StringBuilder sb = new StringBuilder(); + for (Embedding embedding : embeddings) { + sb.append(embedding.getSnapshot().getText()); + } + return sb.toString(); + } + + public void testHtmlEmbedding_01() throws Exception { + checkHtmlEmbedding("html_embedding_01"); + } + +} diff --git a/php/php.blade/test/unit/src/org/netbeans/modules/php/blade/editor/embedding/BladePhpEmbeddingProviderTest.java b/php/php.blade/test/unit/src/org/netbeans/modules/php/blade/editor/embedding/BladePhpEmbeddingProviderTest.java new file mode 100644 index 000000000000..58286bc6fdfe --- /dev/null +++ b/php/php.blade/test/unit/src/org/netbeans/modules/php/blade/editor/embedding/BladePhpEmbeddingProviderTest.java @@ -0,0 +1,65 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.php.blade.editor.embedding; + +import java.util.List; +import org.netbeans.modules.parsing.api.Embedding; +import org.netbeans.modules.parsing.api.Source; +import org.netbeans.modules.parsing.spi.EmbeddingProvider; +import org.netbeans.modules.php.blade.editor.BladeTestBase; + +/** + * + * @author Ondrej Brejla + */ +public class BladePhpEmbeddingProviderTest extends BladeTestBase { + + public BladePhpEmbeddingProviderTest(String testName) { + super(testName); + } + + private void checkPhpEmbedding(final String relFilePath) throws Exception { + checkEmbedding(relFilePath, new BladePhpEmbeddingProvider()); + } + + private void checkEmbedding(final String relFilePath, EmbeddingProvider embeddingProvider) throws Exception { + assertNotNull(embeddingProvider); + String testedFilePath = "testfiles/embedding/" + relFilePath + ".blade.php"; + Source testSource = getTestSource(getTestFile(testedFilePath)); + List embeddings = embeddingProvider.getEmbeddings(testSource.createSnapshot()); + assertDescriptionMatches(testedFilePath, serializableEmbeddings(embeddings), true, ".embedding"); + } + + private String serializableEmbeddings(List embeddings) { + StringBuilder sb = new StringBuilder(); + for (Embedding embedding : embeddings) { + sb.append(embedding.getSnapshot().getText()); + } + return sb.toString(); + } + + public void testPhpInlineEmbedding_01() throws Exception { + checkPhpEmbedding("php_inline_embedding_01"); + } + + public void testPhpInlineEmbedding_02() throws Exception { + checkPhpEmbedding("php_inline_embedding_02"); + } + +} diff --git a/php/php.blade/test/unit/src/org/netbeans/modules/php/blade/editor/format/BladeFormatterTest.java b/php/php.blade/test/unit/src/org/netbeans/modules/php/blade/editor/format/BladeFormatterTest.java new file mode 100644 index 000000000000..247d2169be26 --- /dev/null +++ b/php/php.blade/test/unit/src/org/netbeans/modules/php/blade/editor/format/BladeFormatterTest.java @@ -0,0 +1,43 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.netbeans.modules.php.blade.editor.format; + +/** + * + * + */ +public class BladeFormatterTest extends BladeFormatterTestBase { + + public BladeFormatterTest(String testName) { + super(testName); + } + + public void testIfBlockFormat_01() throws Exception{ + reformatFile("if_block_01"); + } + + public void testDirectiveBlocksFormat_01() throws Exception{ + reformatFile("directive_blocks_01"); + } + + public void testDirectiveBlocksFormat_02() throws Exception{ + reformatFile("directive_blocks_02"); + } +} diff --git a/php/php.blade/test/unit/src/org/netbeans/modules/php/blade/editor/format/BladeFormatterTestBase.java b/php/php.blade/test/unit/src/org/netbeans/modules/php/blade/editor/format/BladeFormatterTestBase.java new file mode 100644 index 000000000000..1451bfbaf1ce --- /dev/null +++ b/php/php.blade/test/unit/src/org/netbeans/modules/php/blade/editor/format/BladeFormatterTestBase.java @@ -0,0 +1,108 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.php.blade.editor.format; + +import javax.swing.text.Document; +import org.netbeans.api.html.lexer.HTMLTokenId; +import org.netbeans.api.lexer.Language; +import org.netbeans.editor.BaseDocument; +import org.netbeans.lib.lexer.test.TestLanguageProvider; +import org.netbeans.modules.csl.api.Formatter; +import org.netbeans.modules.csl.api.test.CslTestBase.IndentPrefs; +import org.netbeans.modules.php.blade.editor.BladeTestBase; +import org.openide.cookies.EditorCookie; +import org.openide.filesystems.FileObject; +import org.openide.loaders.DataObject; +import org.openide.util.Lookup; +import org.openide.util.test.MockLookup; + +/** + * + * + */ +public abstract class BladeFormatterTestBase extends BladeTestBase { + + public BladeFormatterTestBase(String testName) { + super(testName); + } + + @Override + protected void setUp() throws Exception { + super.setUp(); + MockLookup.setInstances(new TestLanguageProvider()); + + // AbstractIndenter.inUnitTestRun = true; + + // init TestLanguageProvider + assert Lookup.getDefault().lookup(TestLanguageProvider.class) != null; + + try { + TestLanguageProvider.register(HTMLTokenId.language()); + } catch (IllegalStateException ise) { + // Ignore -- we've already registered this either via layers or other means + } + try { + TestLanguageProvider.register(getPreferredLanguage().getLexerLanguage()); + } catch (IllegalStateException ise) { + // Ignore -- we've already registered this either via layers or other means + } + } + + @Override + protected BaseDocument getDocument(FileObject fo, String mimeType, Language language) { + // for some reason GsfTestBase is not using DataObjects for BaseDocument construction + // which means that for example Java formatter which does call EditorCookie to retrieve + // document will get difference instance of BaseDocument for indentation + try { + DataObject dobj = DataObject.find(fo); + assertNotNull(dobj); + EditorCookie ec = dobj.getLookup().lookup(EditorCookie.class); + assertNotNull(ec); + return (BaseDocument) ec.openDocument(); + } catch (Exception ex) { + fail(ex.toString()); + return null; + } + } + + @Override + protected void configureIndenters(Document document, Formatter formatter, boolean indentOnly, String mimeType) { + // override it because It's already done in setUp() + } + + @Override + public Formatter getFormatter(IndentPrefs preferences) { + return new BladeFormatter(); + } + + @Override + protected boolean runInEQ() { + return true; + } + + protected void reformatFile(String fileName) throws Exception { + reformatFile(fileName, new IndentPrefs(4, 4)); + } + + protected void reformatFile(String fileName, IndentPrefs indentPreferences) throws Exception { + assert fileName != null; + reformatFileContents("testfiles/format/" + fileName + ".blade.php", indentPreferences); //NOI18N + } + +} diff --git a/php/php.blade/test/unit/src/org/netbeans/modules/php/blade/editor/lexer/BladeLexerPerformanceTest.java b/php/php.blade/test/unit/src/org/netbeans/modules/php/blade/editor/lexer/BladeLexerPerformanceTest.java new file mode 100644 index 000000000000..2e0570129973 --- /dev/null +++ b/php/php.blade/test/unit/src/org/netbeans/modules/php/blade/editor/lexer/BladeLexerPerformanceTest.java @@ -0,0 +1,63 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.php.blade.editor.lexer; + +import java.io.File; +import java.util.Date; +import org.netbeans.modules.php.blade.editor.BladeTestBase; +import org.netbeans.api.lexer.TokenHierarchy; +import org.netbeans.api.lexer.Language; +import org.netbeans.modules.php.blade.editor.BladeLanguage; +import org.netbeans.modules.php.blade.editor.BladeUtils; + +/** + * + * @author bogdan + */ +public class BladeLexerPerformanceTest extends BladeTestBase { + + public BladeLexerPerformanceTest(String testName) { + super(testName); + } + + public void testFile_01() throws Exception { + testFile("perf_test_01"); + } + + private void testFile(String filename) throws Exception { + Date start = new Date(); + File testFile = new File(getDataDir(), "testfiles/lexer/blade/performance/" + filename + ".blade.php"); + String content = BladeUtils.getFileContent(testFile); + BladeLanguage lang = new BladeLanguage(); + Language language = lang.getLexerLanguage(); + TokenHierarchy.create(content, language); + Date end = new Date(); + long time = end.getTime() - start.getTime(); + long fileSize = testFile.getTotalSpace() / 1024; + String output = String.format( + "Lexer tokenization of file(%s: %sKB) takes: %sms", + testFile.getName(), + fileSize, + time + ); +// + System.out.println(output); + assertTrue("Lexer tokenization time should be below 200", time < 200); + } +} diff --git a/php/php.blade/test/unit/src/org/netbeans/modules/php/blade/editor/lexer/BladeLexerTest.java b/php/php.blade/test/unit/src/org/netbeans/modules/php/blade/editor/lexer/BladeLexerTest.java new file mode 100644 index 000000000000..1b71690ffeb7 --- /dev/null +++ b/php/php.blade/test/unit/src/org/netbeans/modules/php/blade/editor/lexer/BladeLexerTest.java @@ -0,0 +1,101 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.php.blade.editor.lexer; + +import java.io.File; +import org.netbeans.api.lexer.TokenHierarchy; +import org.netbeans.api.lexer.Language; +import org.netbeans.api.lexer.TokenSequence; +import org.netbeans.modules.php.blade.editor.BladeLanguage; +import org.netbeans.modules.php.blade.editor.BladeUtils; + +/** + * + * @author bogdan + */ +public class BladeLexerTest extends BladeLexerTestBase { + + public BladeLexerTest(String testName) { + super(testName); + } + + public void testRawTag_01() throws Exception { + performTest("raw_tag"); + } + + public void testContentTag_01() throws Exception { + performTest("content_tag"); + } + + public void testContentTag_tertiary_expr() throws Exception { + performTest("content_tag_tertiary_expr"); + } + + public void testEscapedTag_01() throws Exception { + performTest("escaped_tag"); + } + + public void testEscapedDirective() throws Exception { + performTest("escaped_directive"); + } + + public void testInclude_01() throws Exception { + performTest("include_01"); + } + + + public void testCssAtRules_01() throws Exception { + performTest("css_at_rules"); + } + + public void testHtmlJavascript_01() throws Exception { + performTest("html_javascript_01"); + } + + @Override + protected String getTestResult(String filename) throws Exception { + String content = BladeUtils.getFileContent(new File(getDataDir(), "testfiles/lexer/blade/" + filename + ".blade.php")); + BladeLanguage lang = new BladeLanguage(); + Language language = lang.getLexerLanguage(); + TokenHierarchy hierarchy = TokenHierarchy.create(content, language); + return createResult(hierarchy.tokenSequence(language)); + } + + private String createResult(TokenSequence ts) throws Exception { + StringBuilder result = new StringBuilder(); + while (ts.moveNext()) { + BladeTokenId tokenId = ts.token().id(); + CharSequence text = ts.token().text(); + result.append("Token #"); + result.append(ts.index()); + result.append(" "); + result.append(tokenId.name()); + String token = BladeUtils.replaceLinesAndTabs(text.toString()); + if (!token.isEmpty()) { + result.append(" "); + result.append("["); + result.append(token); + result.append("]"); + } + result.append("\n"); + } + + return result.toString(); + } +} diff --git a/php/php.blade/test/unit/src/org/netbeans/modules/php/blade/editor/lexer/BladeLexerTestBase.java b/php/php.blade/test/unit/src/org/netbeans/modules/php/blade/editor/lexer/BladeLexerTestBase.java new file mode 100644 index 000000000000..953c650fce66 --- /dev/null +++ b/php/php.blade/test/unit/src/org/netbeans/modules/php/blade/editor/lexer/BladeLexerTestBase.java @@ -0,0 +1,44 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.php.blade.editor.lexer; + +import org.netbeans.modules.php.blade.editor.BladeGoldenFileTestBase; + +/** + * + * @author bogdan + */ +public abstract class BladeLexerTestBase extends BladeGoldenFileTestBase { + + public BladeLexerTestBase(String testName) { + super(testName); + } + + @Override + protected void setUp() throws Exception { + super.setUp(); + clearWorkDir(); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + } + +} diff --git a/php/php.blade/test/unit/src/org/netbeans/modules/php/blade/editor/navigator/BladeNavigatorTest.java b/php/php.blade/test/unit/src/org/netbeans/modules/php/blade/editor/navigator/BladeNavigatorTest.java new file mode 100644 index 000000000000..8f82bd520fb4 --- /dev/null +++ b/php/php.blade/test/unit/src/org/netbeans/modules/php/blade/editor/navigator/BladeNavigatorTest.java @@ -0,0 +1,36 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.php.blade.editor.navigator; + +import org.netbeans.modules.php.blade.editor.BladeTestBase; + +/** + * + * @author bogdan + */ +public class BladeNavigatorTest extends BladeTestBase { + + public BladeNavigatorTest(String testName) { + super(testName); + } + + public void testMultipleBlockDirectivesNavigator() throws Exception { + checkStructure("testfiles/navigator/multiple_block_directives.blade.php"); + } +} diff --git a/php/php.blade/test/unit/src/org/netbeans/modules/php/blade/editor/parser/BladeParserErrorTest.java b/php/php.blade/test/unit/src/org/netbeans/modules/php/blade/editor/parser/BladeParserErrorTest.java new file mode 100644 index 000000000000..36d77d2037cd --- /dev/null +++ b/php/php.blade/test/unit/src/org/netbeans/modules/php/blade/editor/parser/BladeParserErrorTest.java @@ -0,0 +1,56 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.php.blade.editor.parser; + +import org.netbeans.modules.php.blade.editor.BladeTestBase; + +/** + * + * @author bogdan + */ +public class BladeParserErrorTest extends BladeTestBase { + + public BladeParserErrorTest(String testName) { + super(testName); + } + + public void testUnopendSetError_01() throws Exception { + checkErrors("testfiles/parser/unopend/unopend_set_error_01.blade.php"); + } + + public void testUclosedSectionError_01() throws Exception { + checkErrors("testfiles/parser/unopend/unclosed_section_error_01.blade.php"); + } + + public void testInlineSectionNoError_01() throws Exception { + checkErrors("testfiles/parser/unopend/inline_section_noerror_01.blade.php"); + } + + public void testUnclosedDirectiveBracketError_01() throws Exception { + checkErrors("testfiles/parser/unopend/unclosed_directive_bracket_error_01.blade.php"); + } + + public void testWrongForeachSyntaxError() throws Exception { + checkErrors("testfiles/parser/errors/wrong_foreach_syntax_error.blade.php"); + } + + public void testWrongEmptySyntaxError() throws Exception { + checkErrors("testfiles/parser/errors/wrong_empty_block_syntax_error.blade.php"); + } +} diff --git a/php/php.blade/test/unit/src/org/netbeans/modules/php/blade/editor/parser/ParserPerformanceTest.java b/php/php.blade/test/unit/src/org/netbeans/modules/php/blade/editor/parser/ParserPerformanceTest.java new file mode 100644 index 000000000000..5b1b7f53237b --- /dev/null +++ b/php/php.blade/test/unit/src/org/netbeans/modules/php/blade/editor/parser/ParserPerformanceTest.java @@ -0,0 +1,81 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.php.blade.editor.parser; + +import java.util.Collections; +import java.util.Date; +import java.util.List; +import static junit.framework.TestCase.assertNotNull; +import static junit.framework.TestCase.assertTrue; +import org.netbeans.modules.csl.spi.ParserResult; +import org.netbeans.modules.parsing.api.ParserManager; +import org.netbeans.modules.parsing.api.ResultIterator; +import org.netbeans.modules.parsing.api.Source; +import org.netbeans.modules.parsing.api.UserTask; +import org.netbeans.modules.parsing.spi.Parser; +import org.netbeans.modules.php.blade.editor.BladeTestBase; +import org.openide.filesystems.FileObject; + +/** + * + * @author bogdan + */ +public class ParserPerformanceTest extends BladeTestBase { + + public ParserPerformanceTest(String testName) { + super(testName); + } + + public void testFile_01() throws Exception { + testFile("testfiles/parser/performance/perf_test_01.blade.php"); // 1.01MB + } + + private void testFile(String filePath) throws Exception { + FileObject fo = getTestFile(filePath); + Source testSource = getTestSource(fo); + + Date start = new Date(); + ParserManager.parse(Collections.singleton(testSource), new UserTask() { + public @Override + void run(ResultIterator resultIterator) throws Exception { + Parser.Result r = resultIterator.getParserResult(); + assertNotNull(r); + assertTrue(r instanceof ParserResult); + + ParserResult pr = (ParserResult) r; + List diagnostics = pr.getDiagnostics(); + String annotatedSource = annotateErrors(diagnostics); + assertDescriptionMatches(filePath, annotatedSource, false, ".errors"); + } + }); + Date end = new Date(); + + long time = end.getTime() - start.getTime(); + long fileSize = testSource.getFileObject().getSize() / 1024; + String output = String.format( + "Parsing of file(%s: %sKB) takes: %sms", + testSource.getFileObject().getName(), + fileSize, + time + ); +// + System.out.println(output); + assertTrue("Parsing time should be below 650", time < 650); + } +} diff --git a/php/php.blade/test/unit/src/org/netbeans/modules/php/blade/editor/typinghooks/BladeTypedTextInterceptorTest.java b/php/php.blade/test/unit/src/org/netbeans/modules/php/blade/editor/typinghooks/BladeTypedTextInterceptorTest.java new file mode 100644 index 000000000000..0767bc96c345 --- /dev/null +++ b/php/php.blade/test/unit/src/org/netbeans/modules/php/blade/editor/typinghooks/BladeTypedTextInterceptorTest.java @@ -0,0 +1,52 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.php.blade.editor.typinghooks; + +public class BladeTypedTextInterceptorTest extends BladeTypinghooksTestBase { + + public BladeTypedTextInterceptorTest(String testName) { + super(testName); + } + + private void insertChar(String original, char insertText, String expected) throws Exception { + insertChar(original, insertText, expected, null); + } + + private void insertChar(String original, char insertText, String expected, String selection) throws Exception { + insertChar(original, insertText, expected, selection, false); + } + + public void testAutoCompleteContentTag_01() throws Exception { + String original = "{^"; + String expected = "{{^ }}"; + insertChar(original, '{', expected); + } + + public void testAutoCompleteRawContentTag_01() throws Exception { + String original = "{!^"; + String expected = "{!!^ !!}"; + insertChar(original, '!', expected); + } + + public void testAutoCompleteCommentTag_01() throws Exception { + String original = "{{-^"; + String expected = "{{--^ --}}"; + insertChar(original, '-', expected); + } +} diff --git a/php/php.blade/test/unit/src/org/netbeans/modules/php/blade/editor/typinghooks/BladeTypinghooksTestBase.java b/php/php.blade/test/unit/src/org/netbeans/modules/php/blade/editor/typinghooks/BladeTypinghooksTestBase.java new file mode 100644 index 000000000000..6327d8515697 --- /dev/null +++ b/php/php.blade/test/unit/src/org/netbeans/modules/php/blade/editor/typinghooks/BladeTypinghooksTestBase.java @@ -0,0 +1,50 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.php.blade.editor.typinghooks; + +import org.netbeans.modules.php.blade.editor.BladeTestBase; +import static org.netbeans.modules.php.blade.editor.preferences.GeneralPreferencesUtils.ENABLE_AUTO_TAG_COMPLETION; +import org.netbeans.modules.php.blade.editor.preferences.ModulePreferences; + + +public abstract class BladeTypinghooksTestBase extends BladeTestBase { + private boolean autoTagCompletion = false; + public BladeTypinghooksTestBase(String testName) { + super(testName); + } + + @Override + protected void setUp() throws Exception { + super.setUp(); + autoTagCompletion = ModulePreferences.getPreferences().getBoolean(ENABLE_AUTO_TAG_COMPLETION, false); + ModulePreferences.setPrefBoolean(ENABLE_AUTO_TAG_COMPLETION, true); + } + + @Override + protected boolean runInEQ() { + return true; + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + ModulePreferences.setPrefBoolean(ENABLE_AUTO_TAG_COMPLETION, autoTagCompletion); + } + +} diff --git a/php/php.blade/test/unit/src/org/netbeans/modules/php/blade/syntax/antlr4/v10/AntlrColoringLexerTest.java b/php/php.blade/test/unit/src/org/netbeans/modules/php/blade/syntax/antlr4/v10/AntlrColoringLexerTest.java new file mode 100644 index 000000000000..cd646c7abfb1 --- /dev/null +++ b/php/php.blade/test/unit/src/org/netbeans/modules/php/blade/syntax/antlr4/v10/AntlrColoringLexerTest.java @@ -0,0 +1,82 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.php.blade.syntax.antlr4.v10; + +import java.io.File; +import org.antlr.v4.runtime.CharStream; +import org.antlr.v4.runtime.CharStreams; +import org.antlr.v4.runtime.CommonTokenStream; +import org.antlr.v4.runtime.Token; +import org.antlr.v4.runtime.Vocabulary; +import org.netbeans.modules.php.blade.editor.BladeUtils; + +/** + * + * @author bogdan + */ +public class AntlrColoringLexerTest extends AntlrLexerTestBase { + + public AntlrColoringLexerTest(String testName) { + super(testName); + } + + public void testRawTag_01() throws Exception { + performTest("lexer/blade/raw_tag"); + } + + public void testContentTag_01() throws Exception { + performTest("lexer/blade/content_tag"); + } + + public void testEscapedTag_01() throws Exception { + performTest("lexer/blade/escaped_tag"); + } + + @Override + protected String getTestResult(String filename) throws Exception { + String content = BladeUtils.getFileContent(new File(getDataDir(), "testfiles/" + filename + ".blade.php")); + CharStream stream = CharStreams.fromString(content); + BladeAntlrColoringLexer lexer = new BladeAntlrColoringLexer(stream); + CommonTokenStream tokens = new CommonTokenStream(lexer); + tokens.fill(); + return createResult(tokens, lexer.getVocabulary()); + } + + private String createResult(CommonTokenStream tokenStream, Vocabulary vocabulary) throws Exception { + StringBuilder result = new StringBuilder(); + for (Token token : tokenStream.getTokens()) { + int tokenId = token.getType(); + String text = token.getText(); + result.append("Token #"); + result.append(tokenId); + result.append(" "); + result.append(vocabulary.getDisplayName(tokenId)); + String tokenText = BladeUtils.replaceLinesAndTabs(text); + if (!tokenText.isEmpty()) { + result.append(" "); + result.append("["); + result.append(token); + result.append("]"); + } + result.append("\n"); + } + + return result.toString(); + } +} diff --git a/php/php.blade/test/unit/src/org/netbeans/modules/php/blade/syntax/antlr4/v10/AntlrLexerTestBase.java b/php/php.blade/test/unit/src/org/netbeans/modules/php/blade/syntax/antlr4/v10/AntlrLexerTestBase.java new file mode 100644 index 000000000000..573944a71e6f --- /dev/null +++ b/php/php.blade/test/unit/src/org/netbeans/modules/php/blade/syntax/antlr4/v10/AntlrLexerTestBase.java @@ -0,0 +1,44 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.php.blade.syntax.antlr4.v10; + +import org.netbeans.modules.php.blade.editor.BladeGoldenFileTestBase; + +/** + * + * @author bogdan + */ +public abstract class AntlrLexerTestBase extends BladeGoldenFileTestBase { + + public AntlrLexerTestBase(String testName) { + super(testName); + } + + @Override + protected void setUp() throws Exception { + super.setUp(); + clearWorkDir(); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + } + +} diff --git a/php/php.blade/test/unit/src/org/netbeans/modules/php/blade/syntax/antlr4/v10/BladeAntlrParserTestBase.java b/php/php.blade/test/unit/src/org/netbeans/modules/php/blade/syntax/antlr4/v10/BladeAntlrParserTestBase.java new file mode 100644 index 000000000000..0b61ba9ed8fd --- /dev/null +++ b/php/php.blade/test/unit/src/org/netbeans/modules/php/blade/syntax/antlr4/v10/BladeAntlrParserTestBase.java @@ -0,0 +1,44 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.php.blade.syntax.antlr4.v10; + +import org.netbeans.modules.php.blade.editor.BladeTestBase; + +/** + * + * @author bhaidu + */ +public abstract class BladeAntlrParserTestBase extends BladeTestBase { + + public BladeAntlrParserTestBase(String testName) { + super(testName); + } + + @Override + protected void setUp() throws Exception { + super.setUp(); + clearWorkDir(); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + } + +} \ No newline at end of file diff --git a/php/php.blade/test/unit/src/org/netbeans/modules/php/blade/syntax/antlr4/v10/BladeAntrlParserTest.java b/php/php.blade/test/unit/src/org/netbeans/modules/php/blade/syntax/antlr4/v10/BladeAntrlParserTest.java new file mode 100644 index 000000000000..09a48461b4bd --- /dev/null +++ b/php/php.blade/test/unit/src/org/netbeans/modules/php/blade/syntax/antlr4/v10/BladeAntrlParserTest.java @@ -0,0 +1,65 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.php.blade.syntax.antlr4.v10; + +import java.io.File; +import java.util.Date; +import static junit.framework.TestCase.assertTrue; +import org.antlr.v4.runtime.CharStream; +import org.antlr.v4.runtime.CharStreams; +import org.antlr.v4.runtime.CommonTokenStream; + +/** + * + * @author bhaidu + */ +public class BladeAntrlParserTest extends BladeAntlrParserTestBase { + + public BladeAntrlParserTest(String testName) { + super(testName); + } + + public void testSmokeFile_01() throws Exception { + testFile("smoke/test_01"); + } + + private void testFile(String filename) throws Exception { + Date start = new Date(); + File testFile = new File(getDataDir(), "testfiles/parser/" + filename + ".blade.php"); + String content = org.netbeans.modules.php.blade.editor.BladeUtils.getFileContent(testFile); + CharStream stream = CharStreams.fromString(content); + BladeAntlrLexer lexer = new BladeAntlrLexer(stream); + CommonTokenStream tokens = new CommonTokenStream(lexer); + BladeAntlrParser parser = new BladeAntlrParser(tokens); + parser.setBuildParseTree(false); + parser.file(); + Date end = new Date(); + long time = end.getTime() - start.getTime(); + long fileSize = testFile.getTotalSpace() / 1024; + String output = String.format( + "Parsing of file(%s: %sKB) takes: %sms", + testFile.getName(), + fileSize, + time + ); +// + System.out.println(output); + assertTrue(time < 200); + } +} diff --git a/php/php.editor/nbproject/project.xml b/php/php.editor/nbproject/project.xml index 55533801e57d..5de0212dd9a1 100644 --- a/php/php.editor/nbproject/project.xml +++ b/php/php.editor/nbproject/project.xml @@ -601,6 +601,7 @@ org.cakephp.netbeans org.nbphpcouncil.modules.php.yii org.netbeans.modules.php.dbgp + org.netbeans.modules.php.blade org.netbeans.modules.php.nette org.netbeans.modules.php.prado org.netbeans.modules.php.refactoring