From fe19fc521777344569e83fd307c5890e6bec79c3 Mon Sep 17 00:00:00 2001 From: Bogdan Haidu Date: Thu, 13 Nov 2025 21:20:03 +0200 Subject: [PATCH] Support for env files - mime provider for .env files - coloring support - interpolated env key features like completion and declaration finder - partial ticket fix #6677 covering env files - include .env file in ImportantFiles node for web and php projects --- .github/workflows/main.yml | 3 + ide/languages.env/build.xml | 52 +++++ ide/languages.env/licenseinfo.xml | 34 +++ ide/languages.env/manifest.mf | 7 + .../nbproject/project.properties | 20 ++ ide/languages.env/nbproject/project.xml | 218 ++++++++++++++++++ .../modules/languages/env/Bundle.properties | 19 ++ .../languages/env/EnvDeclarationFinder.java | 93 ++++++++ .../languages/env/EnvFileResolver.java | 122 ++++++++++ .../modules/languages/env/EnvKeyHandle.java | 82 +++++++ .../modules/languages/env/EnvLanguage.java | 136 +++++++++++ .../languages/env/EnvOccurencesFinder.java | 116 ++++++++++ .../MarkOccurencesOptionsPanelController.java | 95 ++++++++ .../languages/env/MarkOccurencesPanel.form | 102 ++++++++ .../languages/env/MarkOccurencesPanel.java | 197 ++++++++++++++++ .../languages/env/MarkOccurencesSettings.java | 42 ++++ .../modules/languages/env/SimpleHandle.java | 79 +++++++ .../env/completion/EnvCompletionHandler.java | 200 ++++++++++++++++ .../env/completion/KeyCompletionProposal.java | 72 ++++++ .../env/grammar/antlr4/coloring/.gitignore | 1 + .../antlr4/coloring/EnvAntlrColoringLexer.g4 | 180 +++++++++++++++ .../grammar/antlr4/coloring/LexerAdaptor.java | 47 ++++ .../env/grammar/antlr4/parser/.gitignore | 1 + .../grammar/antlr4/parser/EnvAntlrLexer.g4 | 174 ++++++++++++++ .../grammar/antlr4/parser/EnvAntlrParser.g4 | 51 ++++ .../grammar/antlr4/parser/LexerAdaptor.java | 43 ++++ .../grammar/antlr4/parser/ParserAdaptor.java | 39 ++++ .../languages/env/hints/Bundle.properties | 22 ++ .../env/hints/DuplicateKeyAssignment.java | 77 +++++++ .../languages/env/hints/EnvHintsProvider.java | 152 ++++++++++++ .../env/hints/HintsControllerFactory.java | 43 ++++ .../modules/languages/env/lexer/EnvLexer.java | 70 ++++++ .../languages/env/lexer/EnvTokenId.java | 69 ++++++ .../languages/env/parser/EnvParser.java | 51 ++++ .../languages/env/parser/EnvParserResult.java | 169 ++++++++++++++ .../languages/env/project/EnvFileImpl.java | 57 +++++ .../languages/env/resources/Bundle.properties | 37 +++ .../env/resources/FontAndColors-bluetheme.xml | 25 ++ .../resources/FontAndColors-citylights.xml | 25 ++ .../languages/env/resources/FontAndColors.xml | 35 +++ .../languages/env/resources/TemplateHelp.html | 29 +++ .../languages/env/resources/envColoring.env | 26 +++ .../languages/env/resources/envFile.env | 0 .../languages/env/resources/env_file_16.png | Bin 0 -> 480 bytes .../modules/languages/env/resources/layer.xml | 103 +++++++++ .../languages/env/resources/package-info.java | 29 +++ .../data/testfiles/antlr4/lexer/env01.env | 27 +++ .../testfiles/antlr4/lexer/env01.env.lexer | 84 +++++++ .../test/unit/data/testfiles/lexer/env01.env | 27 +++ .../unit/data/testfiles/lexer/env01.env.lexer | 111 +++++++++ .../modules/languages/env/EnvTestBase.java | 51 ++++ .../modules/languages/env/EnvTestUtils.java | 34 +++ .../antlr4/lexer/EnvAntlrLexerTestBase.java | 67 ++++++ .../grammar/antlr4/lexer/EnvLexerTest.java | 30 +++ .../languages/env/lexer/EnvLexerTest.java | 76 ++++++ ide/web.common/nbproject/project.xml | 1 + nbbuild/cluster.properties | 1 + 57 files changed, 3753 insertions(+) create mode 100644 ide/languages.env/build.xml create mode 100644 ide/languages.env/licenseinfo.xml create mode 100644 ide/languages.env/manifest.mf create mode 100644 ide/languages.env/nbproject/project.properties create mode 100644 ide/languages.env/nbproject/project.xml create mode 100644 ide/languages.env/src/org/netbeans/modules/languages/env/Bundle.properties create mode 100644 ide/languages.env/src/org/netbeans/modules/languages/env/EnvDeclarationFinder.java create mode 100644 ide/languages.env/src/org/netbeans/modules/languages/env/EnvFileResolver.java create mode 100644 ide/languages.env/src/org/netbeans/modules/languages/env/EnvKeyHandle.java create mode 100644 ide/languages.env/src/org/netbeans/modules/languages/env/EnvLanguage.java create mode 100644 ide/languages.env/src/org/netbeans/modules/languages/env/EnvOccurencesFinder.java create mode 100644 ide/languages.env/src/org/netbeans/modules/languages/env/MarkOccurencesOptionsPanelController.java create mode 100644 ide/languages.env/src/org/netbeans/modules/languages/env/MarkOccurencesPanel.form create mode 100644 ide/languages.env/src/org/netbeans/modules/languages/env/MarkOccurencesPanel.java create mode 100644 ide/languages.env/src/org/netbeans/modules/languages/env/MarkOccurencesSettings.java create mode 100644 ide/languages.env/src/org/netbeans/modules/languages/env/SimpleHandle.java create mode 100644 ide/languages.env/src/org/netbeans/modules/languages/env/completion/EnvCompletionHandler.java create mode 100644 ide/languages.env/src/org/netbeans/modules/languages/env/completion/KeyCompletionProposal.java create mode 100644 ide/languages.env/src/org/netbeans/modules/languages/env/grammar/antlr4/coloring/.gitignore create mode 100644 ide/languages.env/src/org/netbeans/modules/languages/env/grammar/antlr4/coloring/EnvAntlrColoringLexer.g4 create mode 100644 ide/languages.env/src/org/netbeans/modules/languages/env/grammar/antlr4/coloring/LexerAdaptor.java create mode 100644 ide/languages.env/src/org/netbeans/modules/languages/env/grammar/antlr4/parser/.gitignore create mode 100644 ide/languages.env/src/org/netbeans/modules/languages/env/grammar/antlr4/parser/EnvAntlrLexer.g4 create mode 100644 ide/languages.env/src/org/netbeans/modules/languages/env/grammar/antlr4/parser/EnvAntlrParser.g4 create mode 100644 ide/languages.env/src/org/netbeans/modules/languages/env/grammar/antlr4/parser/LexerAdaptor.java create mode 100644 ide/languages.env/src/org/netbeans/modules/languages/env/grammar/antlr4/parser/ParserAdaptor.java create mode 100644 ide/languages.env/src/org/netbeans/modules/languages/env/hints/Bundle.properties create mode 100644 ide/languages.env/src/org/netbeans/modules/languages/env/hints/DuplicateKeyAssignment.java create mode 100644 ide/languages.env/src/org/netbeans/modules/languages/env/hints/EnvHintsProvider.java create mode 100644 ide/languages.env/src/org/netbeans/modules/languages/env/hints/HintsControllerFactory.java create mode 100644 ide/languages.env/src/org/netbeans/modules/languages/env/lexer/EnvLexer.java create mode 100644 ide/languages.env/src/org/netbeans/modules/languages/env/lexer/EnvTokenId.java create mode 100644 ide/languages.env/src/org/netbeans/modules/languages/env/parser/EnvParser.java create mode 100644 ide/languages.env/src/org/netbeans/modules/languages/env/parser/EnvParserResult.java create mode 100644 ide/languages.env/src/org/netbeans/modules/languages/env/project/EnvFileImpl.java create mode 100644 ide/languages.env/src/org/netbeans/modules/languages/env/resources/Bundle.properties create mode 100644 ide/languages.env/src/org/netbeans/modules/languages/env/resources/FontAndColors-bluetheme.xml create mode 100644 ide/languages.env/src/org/netbeans/modules/languages/env/resources/FontAndColors-citylights.xml create mode 100644 ide/languages.env/src/org/netbeans/modules/languages/env/resources/FontAndColors.xml create mode 100644 ide/languages.env/src/org/netbeans/modules/languages/env/resources/TemplateHelp.html create mode 100644 ide/languages.env/src/org/netbeans/modules/languages/env/resources/envColoring.env create mode 100644 ide/languages.env/src/org/netbeans/modules/languages/env/resources/envFile.env create mode 100644 ide/languages.env/src/org/netbeans/modules/languages/env/resources/env_file_16.png create mode 100644 ide/languages.env/src/org/netbeans/modules/languages/env/resources/layer.xml create mode 100644 ide/languages.env/src/org/netbeans/modules/languages/env/resources/package-info.java create mode 100644 ide/languages.env/test/unit/data/testfiles/antlr4/lexer/env01.env create mode 100644 ide/languages.env/test/unit/data/testfiles/antlr4/lexer/env01.env.lexer create mode 100644 ide/languages.env/test/unit/data/testfiles/lexer/env01.env create mode 100644 ide/languages.env/test/unit/data/testfiles/lexer/env01.env.lexer create mode 100644 ide/languages.env/test/unit/src/org/netbeans/modules/languages/env/EnvTestBase.java create mode 100644 ide/languages.env/test/unit/src/org/netbeans/modules/languages/env/EnvTestUtils.java create mode 100644 ide/languages.env/test/unit/src/org/netbeans/modules/languages/env/grammar/antlr4/lexer/EnvAntlrLexerTestBase.java create mode 100644 ide/languages.env/test/unit/src/org/netbeans/modules/languages/env/grammar/antlr4/lexer/EnvLexerTest.java create mode 100644 ide/languages.env/test/unit/src/org/netbeans/modules/languages/env/lexer/EnvLexerTest.java diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 768bfb2dd5f6..48d25d499570 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -674,6 +674,9 @@ jobs: - name: ide/jumpto run: ant $OPTS -f ide/jumpto test + - name: ide/languages.env + run: ant $OPTS -f ide/languages.env test + - name: ide/languages.hcl run: ant $OPTS -f ide/languages.hcl test diff --git a/ide/languages.env/build.xml b/ide/languages.env/build.xml new file mode 100644 index 000000000000..a24da2f19c39 --- /dev/null +++ b/ide/languages.env/build.xml @@ -0,0 +1,52 @@ + + + + + + + Builds, tests, and runs the project org.netbeans.modules.languages.env + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ide/languages.env/licenseinfo.xml b/ide/languages.env/licenseinfo.xml new file mode 100644 index 000000000000..95a00da45439 --- /dev/null +++ b/ide/languages.env/licenseinfo.xml @@ -0,0 +1,34 @@ + + + + + src/org/netbeans/modules/languages/env/resources/envFile.env + src/org/netbeans/modules/languages/env/resources/envColoring.env + + + + + src/org/netbeans/modules/languages/env/resources/env_file_16.png + + + + \ No newline at end of file diff --git a/ide/languages.env/manifest.mf b/ide/languages.env/manifest.mf new file mode 100644 index 000000000000..472c50e6675c --- /dev/null +++ b/ide/languages.env/manifest.mf @@ -0,0 +1,7 @@ +Manifest-Version: 1.0 +OpenIDE-Module: org.netbeans.modules.languages.env +OpenIDE-Module-Layer: org/netbeans/modules/languages/env/resources/layer.xml +OpenIDE-Module-Localizing-Bundle: org/netbeans/modules/languages/env/resources/Bundle.properties +OpenIDE-Module-Specification-Version: 0.1 +AutoUpdate-Show-In-Client: true + diff --git a/ide/languages.env/nbproject/project.properties b/ide/languages.env/nbproject/project.properties new file mode 100644 index 000000000000..4c3b5d6a165f --- /dev/null +++ b/ide/languages.env/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/ide/languages.env/nbproject/project.xml b/ide/languages.env/nbproject/project.xml new file mode 100644 index 000000000000..094d7bdccd01 --- /dev/null +++ b/ide/languages.env/nbproject/project.xml @@ -0,0 +1,218 @@ + + + + org.netbeans.modules.apisupport.project + + + org.netbeans.modules.languages.env + + + org.netbeans.api.annotations.common + + + + 1 + 1.59 + + + + org.netbeans.api.templates + + + + 1.39 + + + + org.netbeans.core.multiview + + + + 1 + 1.75 + + + + org.netbeans.libs.antlr4.runtime + + + + 2 + 1.32 + + + + org.netbeans.modules.csl.api + + + + 2 + 2.89 + + + + org.netbeans.modules.csl.types + + + + 1 + 1.31 + + + + org.netbeans.modules.lexer + + + + 2 + 1.94 + + + + org.netbeans.modules.lexer.antlr4 + + + + 1.13 + + + + org.netbeans.modules.options.api + + + + 1 + 1.76 + + + + org.netbeans.modules.parsing.api + + + + 1 + 9.38 + + + + org.netbeans.modules.projectapi + + + + 1 + 1.103 + + + + org.netbeans.modules.web.common + + + + 1.130 + + + + org.openide.awt + + + + 7.99 + + + + org.openide.filesystems + + + + 9.44 + + + + org.openide.util + + + + 9.39 + + + + org.openide.util.lookup + + + + 8.65 + + + + org.openide.util.ui + + + + 9.40 + + + + org.openide.windows + + + + 6.108 + + + + + + unit + + org.netbeans.libs.junit4 + + + + org.netbeans.modules.csl.api + + + + + org.netbeans.modules.csl.types + + + + org.netbeans.modules.lexer + + + + + org.netbeans.modules.nbjunit + + + + + org.openide.util.lookup + + + + + + + + + diff --git a/ide/languages.env/src/org/netbeans/modules/languages/env/Bundle.properties b/ide/languages.env/src/org/netbeans/modules/languages/env/Bundle.properties new file mode 100644 index 000000000000..e023e0beadfa --- /dev/null +++ b/ide/languages.env/src/org/netbeans/modules/languages/env/Bundle.properties @@ -0,0 +1,19 @@ +# 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. + +CTL_OnOff_CheckBox=Mark &Occurrences Of Symbol Under Caret +MarkOccurencesPanel.keepMarksCheckBox.text=Checkbox switching mark occurrences on/off diff --git a/ide/languages.env/src/org/netbeans/modules/languages/env/EnvDeclarationFinder.java b/ide/languages.env/src/org/netbeans/modules/languages/env/EnvDeclarationFinder.java new file mode 100644 index 000000000000..9bc5ea7e77e2 --- /dev/null +++ b/ide/languages.env/src/org/netbeans/modules/languages/env/EnvDeclarationFinder.java @@ -0,0 +1,93 @@ +/* + * 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.languages.env; + +import javax.swing.text.AbstractDocument; +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.modules.csl.api.DeclarationFinder; +import org.netbeans.modules.csl.api.OffsetRange; +import org.netbeans.modules.csl.spi.ParserResult; +import org.netbeans.modules.languages.env.lexer.EnvTokenId; +import org.netbeans.modules.languages.env.parser.EnvParserResult; +import org.openide.filesystems.FileObject; + +public class EnvDeclarationFinder implements DeclarationFinder { + + @Override + public OffsetRange getReferenceSpan(Document document, int caretOffset) { + AbstractDocument doc = (AbstractDocument) document; + doc.readLock(); + try { + TokenHierarchy th = TokenHierarchy.get(doc); + TokenSequence ts = th.tokenSequence(); + ts.move(caretOffset); + ts.movePrevious(); + ts.moveNext(); + Token token = ts.token(); + if ((token.id().equals(EnvTokenId.KEY))) { + int start = ts.offset(); + ts.moveNext(); + + if (ts.token().id().equals(EnvTokenId.INTERPOLATION_DELIMITATOR) + || ts.token().id().equals(EnvTokenId.INTERPOLATION_OPERATOR)) { + int end = ts.offset(); + return new OffsetRange(start, end); + } + + return OffsetRange.NONE; + } else { + return OffsetRange.NONE; + } + } finally { + doc.readUnlock(); + } + } + + @Override + public DeclarationLocation findDeclaration(ParserResult info, int caretOffset) { + EnvParserResult result = (EnvParserResult) info; + TokenSequence ts = info.getSnapshot().getTokenHierarchy().tokenSequence(); + ts.move(caretOffset); + ts.movePrevious(); + ts.moveNext(); + Token token = ts.token(); + + if (!token.id().equals(EnvTokenId.KEY)) { + return DeclarationLocation.NONE; + } + + String ref = String.valueOf(token.text()); + FileObject fo = info.getSnapshot().getSource().getFileObject(); + return findKeyDeclaration(result, ref, caretOffset, fo); + } + + public DeclarationLocation findKeyDeclaration(EnvParserResult result, String key, int caretOffset, FileObject fo) { + OffsetRange position = result.getDefinedKeys().get(key); + + if (position != null) { + EnvKeyHandle handle = new EnvKeyHandle(key, fo); + return new DeclarationFinder.DeclarationLocation(fo, position.getStart(), handle); + } + + return DeclarationLocation.NONE; + } +} diff --git a/ide/languages.env/src/org/netbeans/modules/languages/env/EnvFileResolver.java b/ide/languages.env/src/org/netbeans/modules/languages/env/EnvFileResolver.java new file mode 100644 index 000000000000..ba9c66e09beb --- /dev/null +++ b/ide/languages.env/src/org/netbeans/modules/languages/env/EnvFileResolver.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.languages.env; + +import org.netbeans.api.annotations.common.CheckForNull; +import org.netbeans.api.annotations.common.NonNull; +import org.openide.awt.ActionID; +import org.openide.awt.ActionReference; +import org.openide.awt.ActionReferences; +import org.openide.filesystems.FileObject; +import org.openide.filesystems.MIMEResolver; +import org.openide.util.lookup.ServiceProvider; + +@ActionReferences({ + @ActionReference( + path = "Loaders/text/x-env/Actions", + id = @ActionID(category = "System", id = "org.openide.actions.OpenAction"), + position = 100, + separatorAfter = 200 + ), + @ActionReference( + path = "Loaders/text/x-env/Actions", + id = @ActionID(category = "Edit", id = "org.openide.actions.CutAction"), + position = 300 + ), + @ActionReference( + path = "Loaders/text/x-env/Actions", + id = @ActionID(category = "Edit", id = "org.openide.actions.CopyAction"), + position = 400 + ), + @ActionReference( + path = "Loaders/text/x-env/Actions", + id = @ActionID(category = "Edit", id = "org.openide.actions.PasteAction"), + position = 500, + separatorAfter = 600 + ), + @ActionReference( + path = "Loaders/text/x-env/Actions", + id = @ActionID(category = "Edit", id = "org.openide.actions.DeleteAction"), + position = 700 + ), + @ActionReference( + path = "Loaders/text/x-env/Actions", + id = @ActionID(category = "System", id = "org.openide.actions.RenameAction"), + position = 800, + separatorAfter = 900 + ), + @ActionReference( + path = "Loaders/text/x-env/Actions", + id = @ActionID(category = "System", id = "org.openide.actions.SaveAsTemplateAction"), + position = 1000, + separatorAfter = 1100 + ), + @ActionReference( + path = "Loaders/text/x-env/Actions", + id = @ActionID(category = "System", id = "org.openide.actions.FileSystemAction"), + position = 1200, + separatorAfter = 1300 + ), + @ActionReference( + path = "Loaders/text/x-env/Actions", + id = @ActionID(category = "System", id = "org.openide.actions.ToolsAction"), + position = 1400 + ), + @ActionReference( + path = "Loaders/text/x-env/Actions", + id = @ActionID(category = "System", id = "org.openide.actions.PropertiesAction"), + position = 1500 + ), + @ActionReference( + path = "Editors/text/x-env/Popup", + id = @ActionID(category = "Refactoring", id = "org.netbeans.modules.refactoring.api.ui.WhereUsedAction"), + position = 1600 + ),} +) + +@ServiceProvider(service = MIMEResolver.class) +public class EnvFileResolver extends MIMEResolver { + public static final String ENV_EXT = "env"; //NOI18N + public static final String MIME_TYPE = "text/x-env"; //NOI18N + + public EnvFileResolver() { + super(MIME_TYPE); + } + + @CheckForNull + @Override + public String findMIMEType(@NonNull final FileObject fo) { + final String nameWithExt = fo.getNameExt().toLowerCase(); + + if (nameWithExt.endsWith("." + ENV_EXT)) { //NOI18N + return MIME_TYPE; + } + + //Some application might use .env.example name format + int envPartPosition = 2; + String[] nameParts = nameWithExt.split("\\."); //NOI18N + + //check for previous name part + if (nameParts.length >= envPartPosition && nameParts[nameParts.length - envPartPosition].equals(ENV_EXT)) { + return MIME_TYPE; + } + + return null; + } +} diff --git a/ide/languages.env/src/org/netbeans/modules/languages/env/EnvKeyHandle.java b/ide/languages.env/src/org/netbeans/modules/languages/env/EnvKeyHandle.java new file mode 100644 index 000000000000..4c42bc81e646 --- /dev/null +++ b/ide/languages.env/src/org/netbeans/modules/languages/env/EnvKeyHandle.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.languages.env; + +import java.util.Collections; +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.languages.env.EnvFileResolver.MIME_TYPE; +import org.openide.filesystems.FileObject; + +public class EnvKeyHandle implements ElementHandle { + private final String name; + private final ElementKind kind; + private final FileObject fo; + + public EnvKeyHandle(final String name, final FileObject fo) { + this.name = name; + this.kind = ElementKind.CONSTANT; + this.fo = fo; + } + + @Override + public FileObject getFileObject() { + return fo; + } + + @Override + public String getMimeType() { + return MIME_TYPE; + } + + @Override + public String getName() { + return name; + } + + @Override + public String getIn() { + return null; + } + + @Override + public ElementKind getKind() { + return kind; + } + + @Override + public Set getModifiers() { + return Collections.emptySet(); + } + + @Override + public boolean signatureEquals(ElementHandle handle) { + return name.equals(handle.getName()); + } + + @Override + public OffsetRange getOffsetRange(ParserResult result) { + return OffsetRange.NONE; + } + +} diff --git a/ide/languages.env/src/org/netbeans/modules/languages/env/EnvLanguage.java b/ide/languages.env/src/org/netbeans/modules/languages/env/EnvLanguage.java new file mode 100644 index 000000000000..7c43fd879e14 --- /dev/null +++ b/ide/languages.env/src/org/netbeans/modules/languages/env/EnvLanguage.java @@ -0,0 +1,136 @@ +/* + * 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.languages.env; + +import org.netbeans.api.lexer.Language; +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.HintsProvider; +import org.netbeans.modules.csl.api.OccurrencesFinder; +import org.netbeans.modules.csl.spi.DefaultLanguageConfig; +import org.netbeans.modules.csl.spi.LanguageRegistration; +import static org.netbeans.modules.languages.env.EnvFileResolver.MIME_TYPE; +import org.netbeans.modules.languages.env.completion.EnvCompletionHandler; +import org.netbeans.modules.languages.env.hints.EnvHintsProvider; +import org.netbeans.modules.languages.env.lexer.EnvLexer; +import org.netbeans.modules.languages.env.lexer.EnvTokenId; +import org.netbeans.modules.languages.env.lexer.EnvTokenId.EnvLanguageHierarchy; +import org.netbeans.modules.languages.env.parser.EnvParser; +import org.netbeans.modules.languages.env.parser.EnvParserResult; +import org.netbeans.modules.parsing.spi.Parser; +import org.netbeans.spi.lexer.Lexer; +import org.netbeans.spi.lexer.LexerRestartInfo; +import org.openide.util.*; +import org.openide.windows.TopComponent; + +@LanguageRegistration(mimeType = "text/x-env", useMultiview = true) +public class EnvLanguage extends DefaultLanguageConfig { + + @NbBundle.Messages("Source=&Source") + @MultiViewElement.Registration(displayName = "#Source", + iconBase = "org/netbeans/modules/languages/env/resources/env_file_16.png", + persistenceType = TopComponent.PERSISTENCE_ONLY_OPENED, + preferredID = "env.source", + mimeType = MIME_TYPE, + position = 2) + public static MultiViewEditorElement createMultiViewEditorElement(Lookup context) { + return new MultiViewEditorElement(context); + } + + public EnvLanguage() { + super(); + } + + @Override + public Language getLexerLanguage() { + return language; + } + + @Override + public String getDisplayName() { + return "Env"; // NOI18N + } + + @Override + public String getPreferredExtension() { + return "env"; // NOI18N + } + + @Override + public boolean isIdentifierChar(char c) { + return super.isIdentifierChar(c) || c == '_' || c == '.' || c == '-' || c == '@'; // NOI18N + } + + @Override + public String getLineCommentPrefix() { + return "#"; // NOI18N + } + + @Override + public Parser getParser() { + return new EnvParser(); + } + + @Override + public OccurrencesFinder getOccurrencesFinder() { + return new EnvOccurencesFinder(); + } + + @Override + public boolean hasOccurrencesFinder() { + return true; + } + + @Override + public DeclarationFinder getDeclarationFinder() { + return new EnvDeclarationFinder(); + } + + @Override + public CodeCompletionHandler getCompletionHandler() { + return new EnvCompletionHandler(); + } + + @Override + public boolean hasHintsProvider() { + return true; + } + + @Override + public HintsProvider getHintsProvider() { + return new EnvHintsProvider(); + } + + private static final Language language + = new EnvLanguageHierarchy() { + + @Override + protected String mimeType() { + return MIME_TYPE; + } + + @Override + protected Lexer createLexer(LexerRestartInfo info) { + return new EnvLexer(info); + } + + }.language(); +} \ No newline at end of file diff --git a/ide/languages.env/src/org/netbeans/modules/languages/env/EnvOccurencesFinder.java b/ide/languages.env/src/org/netbeans/modules/languages/env/EnvOccurencesFinder.java new file mode 100644 index 000000000000..dd8adb460b6d --- /dev/null +++ b/ide/languages.env/src/org/netbeans/modules/languages/env/EnvOccurencesFinder.java @@ -0,0 +1,116 @@ +/* + * 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.languages.env; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import org.netbeans.api.lexer.Token; +import org.netbeans.api.lexer.TokenHierarchy; +import org.netbeans.api.lexer.TokenSequence; +import org.netbeans.modules.csl.api.ColoringAttributes; +import org.netbeans.modules.csl.api.OccurrencesFinder; +import org.netbeans.modules.csl.api.OffsetRange; +import org.netbeans.modules.languages.env.lexer.EnvTokenId; +import org.netbeans.modules.languages.env.parser.EnvParserResult; +import org.netbeans.modules.parsing.spi.Scheduler; +import org.netbeans.modules.parsing.spi.SchedulerEvent; + +public class EnvOccurencesFinder extends OccurrencesFinder { + + private int caretPosition; + private boolean cancelled; + private final Map occurrences = new HashMap<>(); + + public EnvOccurencesFinder() { + } + + @Override + public void setCaretPosition(int position) { + caretPosition = position; + } + + @Override + public Map getOccurrences() { + return Collections.unmodifiableMap(occurrences); + } + + @Override + public void run(EnvParserResult result, SchedulerEvent event) { + occurrences.clear(); + if (checkAndResetCancel()) { + return; + } + computeOccurrences(result); + } + + @Override + public int getPriority() { + return 200; + } + + @Override + public Class getSchedulerClass() { + return Scheduler.CURSOR_SENSITIVE_TASK_SCHEDULER; + } + + @Override + public void cancel() { + this.cancelled = true; + } + + @Override + public boolean isKeepMarks() { + return true; + } + + @Override + public boolean isMarkOccurrencesEnabled() { + return true; + } + + private boolean checkAndResetCancel() { + if (cancelled) { + cancelled = false; + return true; + } + return false; + } + + private void computeOccurrences(EnvParserResult result) { + TokenHierarchy tokenHierarchy = result.getSnapshot().getTokenHierarchy(); + TokenSequence ts = tokenHierarchy.tokenSequence(); + ts.move(caretPosition); + if (ts.movePrevious()) { + Token token = ts.token(); + + if (!token.id().equals(EnvTokenId.KEY)) { + ts.moveNext(); + token = ts.token(); + } + + if (token.id().equals(EnvTokenId.KEY)) { + String tokenText = token.text().toString(); + for (OffsetRange occurance : result.getOccurrences(tokenText)) { + occurrences.put(occurance, ColoringAttributes.MARK_OCCURRENCES); + } + } + } + } +} diff --git a/ide/languages.env/src/org/netbeans/modules/languages/env/MarkOccurencesOptionsPanelController.java b/ide/languages.env/src/org/netbeans/modules/languages/env/MarkOccurencesOptionsPanelController.java new file mode 100644 index 000000000000..5b6473cba003 --- /dev/null +++ b/ide/languages.env/src/org/netbeans/modules/languages/env/MarkOccurencesOptionsPanelController.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.languages.env; + +import java.beans.PropertyChangeListener; +import java.beans.PropertyChangeSupport; +import javax.swing.JComponent; +import org.netbeans.spi.options.OptionsPanelController; +import org.openide.util.HelpCtx; +import org.openide.util.Lookup; + +public final class MarkOccurencesOptionsPanelController extends OptionsPanelController { + + private MarkOccurencesPanel panel; + + private final PropertyChangeSupport pcs = new PropertyChangeSupport(this); + private boolean changed; + + @Override + public void update() { + getPanel().load(this); + } + + @Override + public void applyChanges() { + getPanel().store(); + } + + @Override + public void cancel() { + // need not do anything special, if no changes have been persisted yet + } + + @Override + public boolean isValid() { + return true; // Always valid + } + + @Override + public boolean isChanged() { + return getPanel().changed(); + } + + @Override + public HelpCtx getHelpCtx() { + return new HelpCtx("netbeans.optionsDialog.env.markoccurrences"); // NOI18N + } + + @Override + public synchronized JComponent getComponent(Lookup masterLookup) { + return getPanel(); + } + + public synchronized MarkOccurencesPanel getPanel() { + if (panel == null) { + panel = new MarkOccurencesPanel(this); + } + return panel; + } + + @Override + public void addPropertyChangeListener(PropertyChangeListener l) { + pcs.addPropertyChangeListener(l); + } + + @Override + public void removePropertyChangeListener(PropertyChangeListener l) { + pcs.removePropertyChangeListener(l); + } + + void changed() { + if (!changed) { + changed = true; + pcs.firePropertyChange(OptionsPanelController.PROP_CHANGED, false, true); + } + pcs.firePropertyChange(OptionsPanelController.PROP_VALID, null, null); + } + +} diff --git a/ide/languages.env/src/org/netbeans/modules/languages/env/MarkOccurencesPanel.form b/ide/languages.env/src/org/netbeans/modules/languages/env/MarkOccurencesPanel.form new file mode 100644 index 000000000000..1bb86405ec5b --- /dev/null +++ b/ide/languages.env/src/org/netbeans/modules/languages/env/MarkOccurencesPanel.form @@ -0,0 +1,102 @@ + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ide/languages.env/src/org/netbeans/modules/languages/env/MarkOccurencesPanel.java b/ide/languages.env/src/org/netbeans/modules/languages/env/MarkOccurencesPanel.java new file mode 100644 index 000000000000..7201736e1b77 --- /dev/null +++ b/ide/languages.env/src/org/netbeans/modules/languages/env/MarkOccurencesPanel.java @@ -0,0 +1,197 @@ +/* + * 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.languages.env; + +import java.awt.event.ItemEvent; +import java.awt.event.ItemListener; +import java.util.ArrayList; +import java.util.List; +import java.util.prefs.BackingStoreException; +import java.util.prefs.Preferences; +import javax.swing.JCheckBox; +import javax.swing.JPanel; +import org.openide.util.Exceptions; + +public class MarkOccurencesPanel extends JPanel { + + private static final boolean DEFAULT_VALUE = true; + private List boxes; + private MarkOccurencesOptionsPanelController controller; + + /* Creates new form MarkOccurencesPanel */ + public MarkOccurencesPanel(MarkOccurencesOptionsPanelController controller) { + initComponents(); + fillBoxes(); + addListeners(); + load(controller); + } + + public void load(MarkOccurencesOptionsPanelController controller) { + this.controller = controller; + + Preferences node = MarkOccurencesSettings.getCurrentNode(); + + for (JCheckBox box : boxes) { + box.setSelected(node.getBoolean(box.getActionCommand(), DEFAULT_VALUE)); + } + + componentsSetEnabled(); + + } + + public void store() { + Preferences node = MarkOccurencesSettings.getCurrentNode(); + for (javax.swing.JCheckBox box : boxes) { + boolean value = box.isSelected(); + boolean original = node.getBoolean(box.getActionCommand(), + DEFAULT_VALUE); + + if (value != original) { + node.putBoolean(box.getActionCommand(), value); + } + } + try { + node.flush(); + } catch (BackingStoreException ex) { + Exceptions.printStackTrace(ex); + } + } + + public boolean changed() { + Preferences node = MarkOccurencesSettings.getCurrentNode(); + for (JCheckBox box : boxes) { + boolean value = box.isSelected(); + boolean original = node.getBoolean(box.getActionCommand(), DEFAULT_VALUE); + if (value != original) { + return true; + } + } + return false; + } + + + /** 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() { + java.awt.GridBagConstraints gridBagConstraints; + + onOffCheckBox = new javax.swing.JCheckBox(); + keepMarksCheckBox = new javax.swing.JCheckBox(); + + setBorder(javax.swing.BorderFactory.createEmptyBorder(8, 8, 8, 8)); + setFocusCycleRoot(true); + setFocusTraversalPolicy(new java.awt.FocusTraversalPolicy() { + public java.awt.Component getDefaultComponent(java.awt.Container focusCycleRoot){ + return onOffCheckBox; + }//end getDefaultComponent + + public java.awt.Component getFirstComponent(java.awt.Container focusCycleRoot){ + return onOffCheckBox; + }//end getFirstComponent + + public java.awt.Component getLastComponent(java.awt.Container focusCycleRoot){ + return onOffCheckBox; + }//end getLastComponent + + public java.awt.Component getComponentAfter(java.awt.Container focusCycleRoot, java.awt.Component aComponent){ + return onOffCheckBox;//end getComponentAfter + } + public java.awt.Component getComponentBefore(java.awt.Container focusCycleRoot, java.awt.Component aComponent){ + return onOffCheckBox;//end getComponentBefore + + }} + ); + setLayout(new java.awt.GridBagLayout()); + + org.openide.awt.Mnemonics.setLocalizedText(onOffCheckBox, org.openide.util.NbBundle.getMessage(MarkOccurencesPanel.class, "CTL_OnOff_CheckBox")); // NOI18N + onOffCheckBox.setBorder(javax.swing.BorderFactory.createEmptyBorder(0, 0, 0, 0)); + gridBagConstraints = new java.awt.GridBagConstraints(); + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = 0; + gridBagConstraints.gridwidth = java.awt.GridBagConstraints.REMAINDER; + gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST; + gridBagConstraints.weightx = 1.0; + gridBagConstraints.insets = new java.awt.Insets(0, 0, 12, 0); + add(onOffCheckBox, gridBagConstraints); + onOffCheckBox.getAccessibleContext().setAccessibleName(org.openide.util.NbBundle.getMessage(MarkOccurencesPanel.class, "MarkOccurrencesPanel.onOffCheckBox.AccessibleContext.accessibleName")); // NOI18N + onOffCheckBox.getAccessibleContext().setAccessibleDescription(org.openide.util.NbBundle.getMessage(MarkOccurencesPanel.class, "ACSD_OnOff_CB")); // NOI18N + + keepMarksCheckBox.setMnemonic('s'); + org.openide.awt.Mnemonics.setLocalizedText(keepMarksCheckBox, org.openide.util.NbBundle.getMessage(MarkOccurencesPanel.class, "MarkOccurencesPanel.keepMarksCheckBox.text")); // NOI18N + gridBagConstraints = new java.awt.GridBagConstraints(); + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = 1; + gridBagConstraints.gridwidth = java.awt.GridBagConstraints.REMAINDER; + gridBagConstraints.gridheight = java.awt.GridBagConstraints.REMAINDER; + gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST; + gridBagConstraints.weighty = 1.0; + gridBagConstraints.insets = new java.awt.Insets(0, 20, 8, 0); + add(keepMarksCheckBox, gridBagConstraints); + keepMarksCheckBox.getAccessibleContext().setAccessibleName(org.openide.util.NbBundle.getMessage(MarkOccurencesPanel.class, "MarkOccurencesPanel.keepMarksCheckBox.text")); // NOI18N + + getAccessibleContext().setAccessibleName(org.openide.util.NbBundle.getMessage(MarkOccurencesPanel.class, "MarkOccurrencesPanel.AccessibleContext.accessibleName")); // NOI18N + getAccessibleContext().setAccessibleDescription(org.openide.util.NbBundle.getMessage(MarkOccurencesPanel.class, "MarkOccurrencesPanel.AccessibleContext.accessibleDescription")); // NOI18N + }// //GEN-END:initComponents + + + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JCheckBox keepMarksCheckBox; + private javax.swing.JCheckBox onOffCheckBox; + // End of variables declaration//GEN-END:variables + // End of variables declaration + + private void fillBoxes() { + boxes = new ArrayList<>(); + boxes.add(onOffCheckBox); + boxes.add(keepMarksCheckBox); + onOffCheckBox.setActionCommand(MarkOccurencesSettings.ON_OFF); + keepMarksCheckBox.setActionCommand(MarkOccurencesSettings.KEEP_MARKS); + } + + private void addListeners() { + ItemListener itemListener = new CheckItemListener(); + for (JCheckBox box : boxes) { + box.addItemListener(itemListener); + } + } + + private void componentsSetEnabled() { + for (int i = 1; i < boxes.size(); i++) { + boxes.get(i).setEnabled(onOffCheckBox.isSelected()); // Switch off the other boxes + } + } + + private class CheckItemListener implements ItemListener { + + @Override + public void itemStateChanged(ItemEvent evt) { + if (evt.getSource() == onOffCheckBox) { + componentsSetEnabled(); + } + controller.changed(); + } + + } + +} diff --git a/ide/languages.env/src/org/netbeans/modules/languages/env/MarkOccurencesSettings.java b/ide/languages.env/src/org/netbeans/modules/languages/env/MarkOccurencesSettings.java new file mode 100644 index 000000000000..e193fbdce1a9 --- /dev/null +++ b/ide/languages.env/src/org/netbeans/modules/languages/env/MarkOccurencesSettings.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.languages.env; + +import java.util.prefs.Preferences; +import org.openide.util.NbPreferences; + +public final class MarkOccurencesSettings { + + private static final String MARK_OCCURENCES = "MarkOccurences"; // NOI18N + public static final String ON_OFF = "OnOff"; // NOI18N + public static final String KEEP_MARKS = "KeepMarks"; // NOI18N + + private MarkOccurencesSettings() { + } + + public static Preferences getCurrentNode() { + Preferences preferences = NbPreferences.forModule(MarkOccurencesOptionsPanelController.class); + return preferences.node("env").node(MARK_OCCURENCES).node(getCurrentProfileId()); + } + + private static String getCurrentProfileId() { + return "default"; // NOI18N + } + +} diff --git a/ide/languages.env/src/org/netbeans/modules/languages/env/SimpleHandle.java b/ide/languages.env/src/org/netbeans/modules/languages/env/SimpleHandle.java new file mode 100644 index 000000000000..faaf7ae3dddb --- /dev/null +++ b/ide/languages.env/src/org/netbeans/modules/languages/env/SimpleHandle.java @@ -0,0 +1,79 @@ +/* + * 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.languages.env; + +import java.util.Collections; +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.openide.filesystems.FileObject; + +public class SimpleHandle implements ElementHandle { + private final String name; + private final ElementKind kind; + + public SimpleHandle(final String name, final ElementKind kind) { + this.name = name; + this.kind = kind; + } + + @Override + public FileObject getFileObject() { + return null; + } + + @Override + public String getMimeType() { + return null; + } + + @Override + public String getName() { + return name; + } + + @Override + public String getIn() { + return null; + } + + @Override + public ElementKind getKind() { + return kind; + } + + @Override + public Set getModifiers() { + return Collections.emptySet(); + } + + @Override + public boolean signatureEquals(ElementHandle handle) { + return name.equals(handle.getName()); + } + + @Override + public OffsetRange getOffsetRange(ParserResult result) { + return OffsetRange.NONE; + } + +} diff --git a/ide/languages.env/src/org/netbeans/modules/languages/env/completion/EnvCompletionHandler.java b/ide/languages.env/src/org/netbeans/modules/languages/env/completion/EnvCompletionHandler.java new file mode 100644 index 000000000000..0d2f332886e3 --- /dev/null +++ b/ide/languages.env/src/org/netbeans/modules/languages/env/completion/EnvCompletionHandler.java @@ -0,0 +1,200 @@ +/* + * 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.languages.env.completion; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.Callable; +import javax.swing.text.Document; +import javax.swing.text.AbstractDocument; +import javax.swing.text.JTextComponent; +import org.netbeans.api.annotations.common.NonNull; +import org.netbeans.api.lexer.Token; +import org.netbeans.api.lexer.TokenHierarchy; +import org.netbeans.api.lexer.TokenSequence; +import org.netbeans.modules.csl.api.CodeCompletionContext; +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.languages.env.EnvKeyHandle; +import org.netbeans.modules.languages.env.lexer.EnvTokenId; +import org.netbeans.modules.languages.env.parser.EnvParserResult; +import org.openide.filesystems.FileObject; + +public class EnvCompletionHandler implements CodeCompletionHandler2 { + + @Override + public Documentation documentElement(ParserResult info, ElementHandle element, Callable cancel) { + return null; + } + + @Override + public CodeCompletionResult complete(CodeCompletionContext context) { + + if (CancelSupport.getDefault().isCancelled()) { + return CodeCompletionResult.NONE; + } + + if (!(context.getParserResult() instanceof EnvParserResult)) { + return CodeCompletionResult.NONE; + } + + EnvParserResult parserResult = (EnvParserResult) context.getParserResult(); + + //only interpolation are relevant + if (parserResult.getInterpolationOccurences().isEmpty()) { + return CodeCompletionResult.NONE; + } + + boolean isInInterpolationContext = false; + + for (Map.Entry> entry : parserResult.getInterpolationOccurences().entrySet()) { + for (OffsetRange range : entry.getValue()) { + if (range.containsInclusive(context.getCaretOffset())) { + isInInterpolationContext = true; + break; + } + } + } + + if (!isInInterpolationContext) { + return CodeCompletionResult.NONE; + } + + int offset = context.getCaretOffset(); + FileObject fo = parserResult.getSnapshot().getSource().getFileObject(); + + final List completionProposals = new ArrayList<>(); + + String contextPrefix = context.getPrefix(); + + for (Map.Entry entry : parserResult.getDefinedKeys().entrySet()) { + if (entry.getValue().getEnd() > offset) { + continue; + } + if (entry.getKey().startsWith(contextPrefix)) { + int anchorOffset = computeAnchorOffset(contextPrefix, offset); + EnvKeyHandle handle = new EnvKeyHandle(entry.getKey(), fo); + completionProposals.add(new KeyCompletionProposal(handle, anchorOffset)); + } + } + + return new DefaultCompletionResult(completionProposals, false); + } + + @Override + public String document(ParserResult info, ElementHandle element) { + return null; + } + + @Override + public ElementHandle resolveLink(String link, ElementHandle originalHandle) { + return null; + } + + @Override + public String getPrefix(ParserResult info, int caretOffset, boolean upToOffset) { + return PrefixResolver.create(info, caretOffset).resolve(); + } + + @Override + public QueryType getAutoQuery(JTextComponent component, String typedText) { + QueryType result = QueryType.ALL_COMPLETION; + if (typedText.length() == 0 || typedText.isBlank()) { + result = QueryType.NONE; + } + + return result; + } + + @Override + public String resolveTemplateVariable(String variable, ParserResult info, int caretOffset, String name, Map parameters) { + return null; + } + + @Override + public Set getApplicableTemplates(Document doc, int selectionBegin, int selectionEnd) { + return null; + } + + @Override + public ParameterInfo parameters(ParserResult info, int caretOffset, CompletionProposal proposal) { + return ParameterInfo.NONE; + } + + private int computeAnchorOffset(@NonNull String prefix, int offset) { + return offset - prefix.length(); + } + + private static final class PrefixResolver { + + private final ParserResult info; + private final int offset; + + static PrefixResolver create(ParserResult info, int offset) { + return new PrefixResolver(info, offset); + } + + private PrefixResolver(ParserResult info, int offset) { + this.info = info; + this.offset = offset; + } + + private String resolve() { + AbstractDocument doc = (AbstractDocument) info.getSnapshot().getSource().getDocument(false); + doc.readLock(); + try { + TokenHierarchy th = TokenHierarchy.get(doc); + TokenSequence ts = th.tokenSequence(); + ts.move(offset); + ts.movePrevious(); + ts.moveNext(); + Token token = ts.token(); + String tokenText = token.text().toString(); + + if (token.id().equals(EnvTokenId.INTERPOLATION_OPERATOR)) { + if (tokenText.equals("{")) { //NOI18N + ts.moveNext(); + token = ts.token(); + } else { + ts.movePrevious(); + token = ts.token(); + } + + if (token.id().equals(EnvTokenId.INTERPOLATION_OPERATOR)) { + return token.text().toString(); + } + } + + return null; + } finally { + doc.readUnlock(); + } + } + } +} diff --git a/ide/languages.env/src/org/netbeans/modules/languages/env/completion/KeyCompletionProposal.java b/ide/languages.env/src/org/netbeans/modules/languages/env/completion/KeyCompletionProposal.java new file mode 100644 index 000000000000..83ba1a277353 --- /dev/null +++ b/ide/languages.env/src/org/netbeans/modules/languages/env/completion/KeyCompletionProposal.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.languages.env.completion; + +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.spi.DefaultCompletionProposal; +import org.netbeans.modules.languages.env.EnvKeyHandle; + +public class KeyCompletionProposal extends DefaultCompletionProposal { + + private final ElementHandle element; + + public KeyCompletionProposal(EnvKeyHandle element, int anchorOffset) { + this.element = element; + this.anchorOffset = anchorOffset; + } + + @Override + public ElementHandle getElement() { + return element; + } + + @Override + public String getName() { + return element.getName(); + } + + @Override + public String getSortText() { + return getName(); + } + + @Override + public int getAnchorOffset() { + return anchorOffset; + } + + @Override + public ElementKind getKind() { + return element.getKind(); + } + + @Override + public String getLhsHtml(HtmlFormatter formatter) { + formatter.name(getKind(), true); + formatter.appendHtml(""); // NOI18N + formatter.appendHtml(""); // NOI18N + formatter.appendText(getName()); + formatter.appendHtml(""); // NOI18N + formatter.appendHtml(""); // NOI18N + formatter.name(getKind(), false); // NOI18N + return formatter.getText(); + } +} diff --git a/ide/languages.env/src/org/netbeans/modules/languages/env/grammar/antlr4/coloring/.gitignore b/ide/languages.env/src/org/netbeans/modules/languages/env/grammar/antlr4/coloring/.gitignore new file mode 100644 index 000000000000..0d262341919a --- /dev/null +++ b/ide/languages.env/src/org/netbeans/modules/languages/env/grammar/antlr4/coloring/.gitignore @@ -0,0 +1 @@ +EnvAntlr*.java diff --git a/ide/languages.env/src/org/netbeans/modules/languages/env/grammar/antlr4/coloring/EnvAntlrColoringLexer.g4 b/ide/languages.env/src/org/netbeans/modules/languages/env/grammar/antlr4/coloring/EnvAntlrColoringLexer.g4 new file mode 100644 index 000000000000..307f7f57c557 --- /dev/null +++ b/ide/languages.env/src/org/netbeans/modules/languages/env/grammar/antlr4/coloring/EnvAntlrColoringLexer.g4 @@ -0,0 +1,180 @@ +/* + * 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 EnvAntlrColoringLexer; + +@header{package org.netbeans.modules.languages.env.grammar.antlr4.coloring;} + +tokens { + NL, + WS, + COMMENT, + STRING, + VALUE, + KEY, + KEYWORD, + OPERATOR, + ASSIGN_OPERATOR, + DOLLAR, + DELIMITATOR +} + +options { + superClass = LexerAdaptor; + caseInsensitive = true; +} + +fragment Esc + : '\\' + ; + +fragment SQuote + : '\'' + ; + +fragment DQuote + : '"' + ; + +fragment BackTickQuote + : '`' + ; + +fragment SQuoteLiteral + : SQuote (Esc [btnfr"'\\] | ~ ['\\])* SQuote + ; + +fragment NewLine + : [\r\n] + ; + +fragment NewLineComment + : '#' ~ [\r\n]* (NL | EOF) + ; + +fragment Identifier + : [a-z_\u0080-\ufffe][a-z0-9_.\u0080-\ufffe-]*; + +fragment KeyIdentiifier + : [a-z_]+[a-z0-9_]* + ; + +KEY + : KeyIdentiifier + ; +COMMENT + : NewLineComment + ; +ASSIGN_OPERATOR + : ('=' | ':')->pushMode(VarAssign) + ; +NL + : NewLine+ + ; +WS + : [ \t]+ ->skip + ; +ERROR + : . + ; +mode VarAssign; +DB_STRING_OPEN + : DQuote ->type(STRING),pushMode(DbQuoteString) + ; + +B_STRING_OPEN + : BackTickQuote ->type(STRING),pushMode(BackQuotedString) + ; + +SG_STRING_OPEN + : SQuoteLiteral ->type(STRING) + ; +EXIT_COMMENT + : (' ')+ NewLineComment->type(COMMENT), popMode + ; +DELIMITATOR_VAR + : (',' | '|' | ':') ->type(DELIMITATOR) + ; +KEYWORD_VAR + : ( + 'true' | 'false' | 'null' | 'on' | '?'+ + | 'prod' | 'production' | 'live' + | 'development' | 'local' | 'test' + ) {this._input.LA(1) == '\n'}?->type(KEYWORD) + ; +INTERPOLATED_VAR + : '$' {this._input.LA(1) == '{'}? + ->type(DOLLAR),pushMode(StringInterpolation) +; + +//greedy identifier matching +IDENTIFIER_VAR + : Identifier {this._input.LA(1) == '\n'}? + ->type(VALUE) + ; +EXIT_VAR_ASSING : NewLine->type(NL), popMode; +INLINE_WS : [ \t]+->skip; +ANY_VALUE : . ->type(VALUE); + +mode DbQuoteString; + +DBQ_TEXT : (Esc [btnfr"'\\] | ~ [$"\r\n\\])+->type(STRING); +DBQ_INTERPOLATED_VAR + : '$' {this._input.LA(1) == '{'}? + ->type(DOLLAR),pushMode(StringInterpolation) +; +DBQ_STRING_CLOSE : DQuote ->type(STRING),popMode; +ANY_DBQ_TEXT : . ->type(STRING); + +mode BackQuotedString; + +BQ_TEXT : (Esc [btnfr"'`\\] | ~ [$`\r\n\\])+->type(STRING); +BQ_INTERPOLATED_VAR + : '$' {this._input.LA(1) == '{'}? + ->type(DOLLAR),pushMode(StringInterpolation) +; +BQ_STRING_CLOSE : BackTickQuote ->type(STRING),popMode; +ANY_BQ_TEXT : . ->type(STRING); + +mode StringInterpolation; + +CURLY_OPEN + : '{' {this.resetInterpolationKeyAdded();} + ; +CURLY_CLOSE + : '}' ->popMode + ; +INTERPOLATION_VAR + : {!this.keyTokenAdded()}? KeyIdentiifier {this.consumeKeyToken();}->type(KEY) + ; + +/* +from https://dotenvx.com/docs/env-file +${VAR:-default} -> value of VAR if set and non-empty, otherwise default +${VAR-default} -> value of VAR if set, otherwise default + +${VAR:+alternate} -> value of alternate if VAR is set and non-empty, otherwise empty '' +${VAR+alternate} -> value of alternate if VAR is set and non-empty, otherwise empty '' +*/ +INTERPOLATION_OPERATOR + : (':' ('+' | '-')? | '?' | '+' | '-') + ; + +VALUE_INTERPOLATION + : . ->type(VALUE) + ; \ No newline at end of file diff --git a/ide/languages.env/src/org/netbeans/modules/languages/env/grammar/antlr4/coloring/LexerAdaptor.java b/ide/languages.env/src/org/netbeans/modules/languages/env/grammar/antlr4/coloring/LexerAdaptor.java new file mode 100644 index 000000000000..af37d1f0486c --- /dev/null +++ b/ide/languages.env/src/org/netbeans/modules/languages/env/grammar/antlr4/coloring/LexerAdaptor.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.languages.env.grammar.antlr4.coloring; + +import org.antlr.v4.runtime.CharStream; +import org.antlr.v4.runtime.Lexer; + +public abstract class LexerAdaptor extends Lexer { + + private boolean interpolationKeyAdded = false; + + public LexerAdaptor(CharStream input) { + super(input); + } + + public void setInterpolationKeyAddedState(boolean state) { + interpolationKeyAdded = state; + } + + public void consumeKeyToken() { + interpolationKeyAdded = true; + } + + public void resetInterpolationKeyAdded() { + interpolationKeyAdded = false; + } + + public boolean keyTokenAdded() { + return interpolationKeyAdded; + } +} diff --git a/ide/languages.env/src/org/netbeans/modules/languages/env/grammar/antlr4/parser/.gitignore b/ide/languages.env/src/org/netbeans/modules/languages/env/grammar/antlr4/parser/.gitignore new file mode 100644 index 000000000000..0d262341919a --- /dev/null +++ b/ide/languages.env/src/org/netbeans/modules/languages/env/grammar/antlr4/parser/.gitignore @@ -0,0 +1 @@ +EnvAntlr*.java diff --git a/ide/languages.env/src/org/netbeans/modules/languages/env/grammar/antlr4/parser/EnvAntlrLexer.g4 b/ide/languages.env/src/org/netbeans/modules/languages/env/grammar/antlr4/parser/EnvAntlrLexer.g4 new file mode 100644 index 000000000000..f4e1def01c43 --- /dev/null +++ b/ide/languages.env/src/org/netbeans/modules/languages/env/grammar/antlr4/parser/EnvAntlrLexer.g4 @@ -0,0 +1,174 @@ +/* + * 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 EnvAntlrLexer; + +@header{package org.netbeans.modules.languages.env.grammar.antlr4.parser;} + +tokens { + NL, + DOLLAR +} + +options { + superClass = LexerAdaptor; + caseInsensitive = true; +} + +fragment Esc + : '\\' + ; + +fragment SQuote + : '\'' + ; + +fragment DQuote + : '"' + ; + +fragment BackTickQuote + : '`' + ; + +fragment SQuoteLiteral + : SQuote (Esc [btnfr"'\\] | ~ ['\\])* SQuote + ; + +fragment NewLine + : [\r\n] + ; + +fragment NewLineComment + : '#' ~ [\r\n]* (NL | EOF) + ; + +fragment Identifier + : [a-z_\u0080-\ufffe][a-z0-9_.\u0080-\ufffe-]* + ; + +fragment KeyIdentiifier + : [a-z_]+[a-z0-9_]* + ; + +KEY + : KeyIdentiifier + ; +COMMENT + : NewLineComment->skip + ; + +ASSIGN_OPERATOR + : ('=' | ':')->pushMode(VarAssign) + ; + +NL + : NewLine+ + ; + +WS + : [ \t]+ ->skip + ; +ERROR + : . + ; + +mode VarAssign; + +DB_STRING_OPEN + : DQuote ->skip,pushMode(DbQuoteString) + ; + +B_STRING_OPEN + : BackTickQuote ->skip,pushMode(BacktickQuotedString) + ; + + +SG_STRING_OPEN + : SQuoteLiteral ->skip + ; + +INTERPOLATED_VAR + : '$' {this._input.LA(1) == '{'}? + ->type(DOLLAR),pushMode(StringInterpolation) + ; + +//greedy identifier matching +IDENTIFIER_VAR + : Identifier {this._input.LA(1) == '\n'}? ->skip + ; + +EXIT_VAR_ASSING : NewLine->type(NL), popMode; +INLINE_WS : [ \t]->skip; +ANY_VALUE : . ->skip; + +mode DbQuoteString; + +DBQ_TEXT + : (Esc [btnfr"'\\] | ~ [$"\r\n\\])+->skip + ; + +DBQ_INTERPOLATED_VAR + : '$' {this._input.LA(1) == '{'}? + ->type(DOLLAR),pushMode(StringInterpolation) + ; + +DBQ_STRING_CLOSE + : DQuote ->skip,popMode + ; + +ANY_DBQ_TEXT : . ->skip; + +mode BacktickQuotedString; + +BQ_TEXT + : (Esc [btnfr"'`\\] | ~ [$`\r\n\\])+->skip + ; + +BQ_INTERPOLATED_VAR + : '$' {this._input.LA(1) == '{'}? + ->type(DOLLAR),pushMode(StringInterpolation) + ; + +BQ_STRING_CLOSE + : BackTickQuote ->skip,popMode + ; + +ANY_BQ_TEXT : . ->skip; + +mode StringInterpolation; + +CURLY_OPEN + : '{' {this.resetInterpolationKeyAdded();} + ; + +CURLY_CLOSE + : '}' ->popMode + ; + +INTERPOLATION_VAR + : {!this.keyTokenAdded()}? KeyIdentiifier {this.consumeKeyToken();} ->type(KEY) + ; + +INTERPOLATION_OPERATOR + : (':' ('+' | '-' | '?')? | '?' | '+' | '-') + ; + +VALUE_INTERPOLATION + : . ->skip + ; \ No newline at end of file diff --git a/ide/languages.env/src/org/netbeans/modules/languages/env/grammar/antlr4/parser/EnvAntlrParser.g4 b/ide/languages.env/src/org/netbeans/modules/languages/env/grammar/antlr4/parser/EnvAntlrParser.g4 new file mode 100644 index 000000000000..f765448f43d7 --- /dev/null +++ b/ide/languages.env/src/org/netbeans/modules/languages/env/grammar/antlr4/parser/EnvAntlrParser.g4 @@ -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. + */ +parser grammar EnvAntlrParser; + +@header{package org.netbeans.modules.languages.env.grammar.antlr4.parser;} + +options { + superClass = ParserAdaptor; + tokenVocab = EnvAntlrLexer; + } + +envFile + : line* EOF + ; + +line + : varAssign | newLine + ; + +newLine + : NL + ; + +varAssign + : KEY assignOperator interpolatedKey* (newLine | EOF) + ; + +assignOperator + : ASSIGN_OPERATOR + ; + +interpolatedKey + : DOLLAR + CURLY_OPEN keyName=KEY INTERPOLATION_OPERATOR? CURLY_CLOSE + ; \ No newline at end of file diff --git a/ide/languages.env/src/org/netbeans/modules/languages/env/grammar/antlr4/parser/LexerAdaptor.java b/ide/languages.env/src/org/netbeans/modules/languages/env/grammar/antlr4/parser/LexerAdaptor.java new file mode 100644 index 000000000000..63585a38873f --- /dev/null +++ b/ide/languages.env/src/org/netbeans/modules/languages/env/grammar/antlr4/parser/LexerAdaptor.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.languages.env.grammar.antlr4.parser; + +import org.antlr.v4.runtime.CharStream; +import org.antlr.v4.runtime.Lexer; + +public abstract class LexerAdaptor extends Lexer { + + private boolean interpolationKeyAdded = false; + + public LexerAdaptor(CharStream input) { + super(input); + } + + public void consumeKeyToken() { + interpolationKeyAdded = true; + } + + public void resetInterpolationKeyAdded() { + interpolationKeyAdded = false; + } + + public boolean keyTokenAdded() { + return interpolationKeyAdded; + } +} diff --git a/ide/languages.env/src/org/netbeans/modules/languages/env/grammar/antlr4/parser/ParserAdaptor.java b/ide/languages.env/src/org/netbeans/modules/languages/env/grammar/antlr4/parser/ParserAdaptor.java new file mode 100644 index 000000000000..1239f2b1f7ac --- /dev/null +++ b/ide/languages.env/src/org/netbeans/modules/languages/env/grammar/antlr4/parser/ParserAdaptor.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.languages.env.grammar.antlr4.parser; + +import org.antlr.v4.runtime.Parser; +import org.antlr.v4.runtime.TokenStream; + +public abstract class ParserAdaptor extends Parser { + + public static enum ParserContext { + STANDARD + } + + protected ParserContext parserContext = ParserContext.STANDARD; + + public ParserAdaptor(TokenStream input) { + super(input); + } + + public void setEnvParserContext(ParserContext context){ + this.parserContext = context; + } +} diff --git a/ide/languages.env/src/org/netbeans/modules/languages/env/hints/Bundle.properties b/ide/languages.env/src/org/netbeans/modules/languages/env/hints/Bundle.properties new file mode 100644 index 000000000000..a2a320cd6f65 --- /dev/null +++ b/ide/languages.env/src/org/netbeans/modules/languages/env/hints/Bundle.properties @@ -0,0 +1,22 @@ +# 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. + +DuplicateKeyHintMsg=Duplicate Key Assignment +AST_Rule_DuplicateKey=Duplicate Key Assignment +AST_Rule_DuplicateKeyeDescription=Duplicate Key assignment detected. + +csl-hints/text/x-env/hints/duplicate_key_assignment=Duplicate key assignment \ No newline at end of file diff --git a/ide/languages.env/src/org/netbeans/modules/languages/env/hints/DuplicateKeyAssignment.java b/ide/languages.env/src/org/netbeans/modules/languages/env/hints/DuplicateKeyAssignment.java new file mode 100644 index 000000000000..4b29d984b9d0 --- /dev/null +++ b/ide/languages.env/src/org/netbeans/modules/languages/env/hints/DuplicateKeyAssignment.java @@ -0,0 +1,77 @@ +/* + * 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.languages.env.hints; + +import java.util.Collections; +import java.util.Set; +import java.util.prefs.Preferences; +import javax.swing.JComponent; +import org.netbeans.modules.csl.api.HintSeverity; +import org.netbeans.modules.csl.api.Rule; +import org.netbeans.modules.csl.api.RuleContext; +import static org.netbeans.modules.languages.env.hints.EnvHintsProvider.ENV_HINTS_GROUP_KIND; +import org.openide.util.NbBundle; + +public class DuplicateKeyAssignment implements Rule.AstRule { + + @Override + public boolean getDefaultEnabled() { + return true; + } + + @Override + public JComponent getCustomizer(Preferences node) { + return null; + } + + @Override + public boolean appliesTo(RuleContext context) { + return context instanceof EnvHintsProvider.EnvRuleContext; + } + + @Override + public boolean showInTasklist() { + return true; + } + + @Override + public HintSeverity getDefaultSeverity() { + return HintSeverity.WARNING; + } + + @Override + public Set getKinds() { + return Collections.singleton(ENV_HINTS_GROUP_KIND); + } + + @Override + public String getId() { + return "env.hint.duplicate_key_assignment"; //NOI18N + } + + @Override + public String getDescription() { + return NbBundle.getMessage(DuplicateKeyAssignment.class, "AST_Rule_DuplicateKey"); //NOI18N + } + + @Override + public String getDisplayName() { + return NbBundle.getMessage(DuplicateKeyAssignment.class, "AST_Rule_DuplicateKeyeDescription"); //NOI18N + } +} diff --git a/ide/languages.env/src/org/netbeans/modules/languages/env/hints/EnvHintsProvider.java b/ide/languages.env/src/org/netbeans/modules/languages/env/hints/EnvHintsProvider.java new file mode 100644 index 000000000000..90ff188663b9 --- /dev/null +++ b/ide/languages.env/src/org/netbeans/modules/languages/env/hints/EnvHintsProvider.java @@ -0,0 +1,152 @@ +/* + * 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.languages.env.hints; + +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import org.netbeans.api.options.OptionsDisplayer; +import org.netbeans.modules.csl.api.Error; +import org.netbeans.modules.csl.api.Hint; +import org.netbeans.modules.csl.api.HintFix; +import org.netbeans.modules.csl.api.HintsProvider; +import org.netbeans.modules.csl.api.OffsetRange; +import org.netbeans.modules.csl.api.Rule; +import org.netbeans.modules.csl.api.RuleContext; +import org.netbeans.modules.languages.env.EnvFileResolver; +import org.netbeans.modules.languages.env.parser.EnvParserResult; +import org.openide.util.NbBundle; + +public class EnvHintsProvider implements HintsProvider { + + public static final String ENV_HINTS_GROUP_KIND = "env.option.duplicatekey.hints"; //NOI18N + + @Override + public void computeHints(HintsManager manager, RuleContext context, List hints) { + if (!(context.parserResult instanceof EnvParserResult)) { + return; + } + + EnvParserResult parserResult = (EnvParserResult) context.parserResult; + + Map> allHints = manager.getHints(false, context); + List hintRules = allHints.get(ENV_HINTS_GROUP_KIND); + + if (hintRules == null) { + return; + } + + for (Rule.AstRule astRule : hintRules) { + if (!manager.isEnabled(astRule)) { + continue; + } + if (astRule instanceof DuplicateKeyAssignment) { + for (Map.Entry> entry : parserResult.getKeyDefinitions().entrySet()) { + int listSize = entry.getValue().size(); + + if (listSize == 1) { + continue; + } + + for (OffsetRange range : entry.getValue().subList(1, listSize)) { + hints.add(new Hint(astRule, + NbBundle.getMessage(EnvHintsProvider.class, "DuplicateKeyHintMsg"), //NOI18N + context.parserResult.getSnapshot().getSource().getFileObject(), + range, + configHintsFixList(), + 10)); + } + } + } + } + } + + @Override + public void computeSuggestions(HintsManager manager, RuleContext context, List suggestions, int caretOffset) { + + } + + @Override + public void computeSelectionHints(HintsManager manager, RuleContext context, List suggestions, int start, int end) { + + } + + @Override + public void computeErrors(HintsManager manager, RuleContext context, List hints, List unhandled) { + unhandled.addAll(context.parserResult.getDiagnostics()); + } + + @Override + public void cancel() { + + } + + @Override + public List getBuiltinRules() { + return null; + } + + @Override + public RuleContext createRuleContext() { + return new EnvRuleContext(); + } + + private List configHintsFixList() { + List fixes = new LinkedList<>(); + + fixes.add(new ConfigHintFix()); + + return fixes; + } + + public class EnvRuleContext extends RuleContext { + + public boolean isCancelled() { + return false; + } + + } + + private static class ConfigHintFix implements HintFix { + + public ConfigHintFix() { + } + + @Override + public String getDescription() { + return "Configure Hints"; //NOI18N + } + + @Override + public void implement() throws Exception { + OptionsDisplayer displayer = OptionsDisplayer.getDefault(); + displayer.open("Editor/Hints/" + EnvFileResolver.MIME_TYPE); //NOI18N + } + + @Override + public boolean isSafe() { + return true; + } + + @Override + public boolean isInteractive() { + return false; + } + } +} diff --git a/ide/languages.env/src/org/netbeans/modules/languages/env/hints/HintsControllerFactory.java b/ide/languages.env/src/org/netbeans/modules/languages/env/hints/HintsControllerFactory.java new file mode 100644 index 000000000000..e77f7746ae16 --- /dev/null +++ b/ide/languages.env/src/org/netbeans/modules/languages/env/hints/HintsControllerFactory.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.languages.env.hints; + +import org.netbeans.modules.csl.api.HintsProvider; +import org.netbeans.modules.languages.env.EnvFileResolver; +import org.netbeans.spi.options.OptionsPanelController; +import org.openide.util.NbBundle; + +public class HintsControllerFactory { + + public HintsControllerFactory() { + } + + @OptionsPanelController.SubRegistration( + id = "EnvFileHints", + location = "Env/Hints", + displayName = "#HintsControllerFactory.name" + ) + @NbBundle.Messages("HintsControllerFactory.name=Env File Hints") + public static OptionsPanelController createOptions() { + HintsProvider.HintsManager manager = HintsProvider.HintsManager.getManagerForMimeType(EnvFileResolver.MIME_TYPE); + assert manager != null; + + return manager.getOptionsController(); + } +} diff --git a/ide/languages.env/src/org/netbeans/modules/languages/env/lexer/EnvLexer.java b/ide/languages.env/src/org/netbeans/modules/languages/env/lexer/EnvLexer.java new file mode 100644 index 000000000000..fd486adbccac --- /dev/null +++ b/ide/languages.env/src/org/netbeans/modules/languages/env/lexer/EnvLexer.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.languages.env.lexer; + +import org.netbeans.api.lexer.Token; +import org.netbeans.spi.lexer.LexerRestartInfo; +import org.netbeans.spi.lexer.antlr4.AbstractAntlrLexerBridge; +import org.netbeans.modules.languages.env.grammar.antlr4.coloring.EnvAntlrColoringLexer; +import static org.netbeans.modules.languages.env.grammar.antlr4.coloring.EnvAntlrColoringLexer.*; + +public class EnvLexer extends AbstractAntlrLexerBridge { + public EnvLexer(LexerRestartInfo info) { + super(info, EnvAntlrColoringLexer::new); + } + + @Override + public Object state() { + return new State(lexer); + } + + @Override + protected Token mapToken(org.antlr.v4.runtime.Token antlrToken) { + return switch (antlrToken.getType()) { + case COMMENT -> groupToken(EnvTokenId.COMMENT, COMMENT); + case KEY -> token(EnvTokenId.KEY); + case KEYWORD -> token(EnvTokenId.KEYWORD); + case STRING -> groupToken(EnvTokenId.STRING, STRING); + case VALUE -> groupToken(EnvTokenId.VALUE, VALUE); + case OPERATOR, ASSIGN_OPERATOR -> token(EnvTokenId.OPERATOR); + case CURLY_OPEN, CURLY_CLOSE -> token(EnvTokenId.INTERPOLATION_DELIMITATOR); + case INTERPOLATION_OPERATOR -> token(EnvTokenId.INTERPOLATION_OPERATOR); + case DELIMITATOR -> token(EnvTokenId.DELIMITATOR); + case DOLLAR -> token(EnvTokenId.DOLLAR); + case WS -> groupToken(EnvTokenId.WS, WS); + case NL -> groupToken(EnvTokenId.WS, NL); + default -> groupToken(EnvTokenId.ERROR, ERROR); + }; + } + + private static class State extends AbstractAntlrLexerBridge.LexerState { + final boolean interpolationKeyAdded; + + public State(EnvAntlrColoringLexer lexer) { + super(lexer); + this.interpolationKeyAdded = lexer.keyTokenAdded(); + } + + @Override + public void restore(EnvAntlrColoringLexer lexer) { + super.restore(lexer); + lexer.setInterpolationKeyAddedState(interpolationKeyAdded); + } + } +} diff --git a/ide/languages.env/src/org/netbeans/modules/languages/env/lexer/EnvTokenId.java b/ide/languages.env/src/org/netbeans/modules/languages/env/lexer/EnvTokenId.java new file mode 100644 index 000000000000..b731072adf24 --- /dev/null +++ b/ide/languages.env/src/org/netbeans/modules/languages/env/lexer/EnvTokenId.java @@ -0,0 +1,69 @@ +/* + * 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.languages.env.lexer; + +import java.util.Collection; +import java.util.EnumSet; +import org.netbeans.api.lexer.InputAttributes; +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; + +public enum EnvTokenId implements TokenId { + COMMENT("comment"), + KEY("key"), + STRING("string"), + KEYWORD("keyword"), + VALUE("value"), + DELIMITATOR("delimitator"), + DOLLAR("dollar"), + OPERATOR("operator"), + INTERPOLATION_DELIMITATOR("operator"), + INTERPOLATION_OPERATOR("interpolation_operator"), + WS("whitespace"), + ERROR("error"); + private final String primaryCategory; + + EnvTokenId(String category) { + this.primaryCategory = category; + } + + @Override + + public String primaryCategory() { + return primaryCategory; + } + + public static abstract class EnvLanguageHierarchy extends LanguageHierarchy { + + @Override + protected Collection createTokenIds() { + return EnumSet.allOf(EnvTokenId.class); + } + + @Override + protected LanguageEmbedding embedding(Token token, + LanguagePath languagePath, InputAttributes inputAttributes) { + + return null; + } + } +} diff --git a/ide/languages.env/src/org/netbeans/modules/languages/env/parser/EnvParser.java b/ide/languages.env/src/org/netbeans/modules/languages/env/parser/EnvParser.java new file mode 100644 index 000000000000..f38e206be9e3 --- /dev/null +++ b/ide/languages.env/src/org/netbeans/modules/languages/env/parser/EnvParser.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.languages.env.parser; + +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; + +public class EnvParser extends org.netbeans.modules.parsing.spi.Parser { + + private EnvParserResult lastResult; + + @Override + public void parse(Snapshot snapshot, Task task, SourceModificationEvent event) throws ParseException { + lastResult = new EnvParserResult(snapshot).get(); + } + + @Override + public Result getResult(Task task) throws ParseException { + return lastResult; + } + + @Override + public void addChangeListener(ChangeListener changeListener) { + + } + + @Override + public void removeChangeListener(ChangeListener changeListener) { + + } + +} diff --git a/ide/languages.env/src/org/netbeans/modules/languages/env/parser/EnvParserResult.java b/ide/languages.env/src/org/netbeans/modules/languages/env/parser/EnvParserResult.java new file mode 100644 index 000000000000..b65f1479b50e --- /dev/null +++ b/ide/languages.env/src/org/netbeans/modules/languages/env/parser/EnvParserResult.java @@ -0,0 +1,169 @@ +/* + * 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.languages.env.parser; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.antlr.v4.runtime.ANTLRErrorListener; +import org.antlr.v4.runtime.BaseErrorListener; +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.modules.csl.api.Error; +import org.netbeans.modules.csl.api.OffsetRange; +import org.netbeans.modules.csl.api.Severity; +import org.netbeans.modules.csl.spi.DefaultError; +import org.netbeans.modules.csl.spi.ParserResult; +import org.netbeans.modules.languages.env.grammar.antlr4.parser.EnvAntlrLexer; +import org.netbeans.modules.languages.env.grammar.antlr4.parser.EnvAntlrParser; +import org.netbeans.modules.languages.env.grammar.antlr4.parser.EnvAntlrParserBaseListener; +import org.netbeans.modules.parsing.api.Snapshot; +import org.openide.filesystems.FileObject; + +public class EnvParserResult extends ParserResult { + + private final List errors = new ArrayList<>(); + private final Map definedKeys = new HashMap<>(); + private final Map> keyDefintions = new HashMap<>(); + private final Map> interpolationOccurences = new HashMap<>(); + volatile boolean finished = false; + + public EnvParserResult(Snapshot snapshot) { + super(snapshot); + } + + public EnvParserResult get() { + if (!finished) { + EnvAntlrLexer lexer = new EnvAntlrLexer(CharStreams.fromString(String.valueOf(getSnapshot().getText()))); + EnvAntlrParser parser = new EnvAntlrParser(new CommonTokenStream(lexer)); + + parser.setBuildParseTree(false);//faster parser + parser.addErrorListener(createErrorListener()); + parser.addParseListener(new EnvKeyListener()); + parser.removeErrorListener(ConsoleErrorListener.INSTANCE); + parser.envFile(); + finished = true; + } + return this; + } + + @Override + public List getDiagnostics() { + return errors; + } + + @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 BadgingDefaultError(null, msg, null, getFileObject(), errorPosition, errorPosition, Severity.ERROR, true)); + } + + }; + } + + public final List getOccurrences(String refName) { + ArrayList ret = new ArrayList<>(); + if (keyDefintions.containsKey(refName)) { + ret.add(definedKeys.get(refName)); + } + if (interpolationOccurences.containsKey(refName)) { + ret.addAll(interpolationOccurences.get(refName)); + } + return ret; + } + + private class EnvKeyListener extends EnvAntlrParserBaseListener { + + @Override + public void exitVarAssign(EnvAntlrParser.VarAssignContext ctx) { + Token keyToken = ctx.start; + + if (keyToken == null) { + return; + } + + String keyName = keyToken.getText(); + OffsetRange range = new OffsetRange(keyToken.getStartIndex(), keyToken.getStopIndex() + 1); + definedKeys.putIfAbsent(keyName, range); + keyDefintions.computeIfAbsent(keyName, s -> new ArrayList<>()).add(range); + } + + @Override + public void exitInterpolatedKey(EnvAntlrParser.InterpolatedKeyContext ctx) { + Token keyToken = ctx.keyName; + + if (keyToken == null) { + return; + } + + OffsetRange range = new OffsetRange(keyToken.getStartIndex(), keyToken.getStopIndex() + 1); + interpolationOccurences.computeIfAbsent(keyToken.getText(), s -> new ArrayList<>()).add(range); + } + } + + public Map getDefinedKeys() { + return Collections.unmodifiableMap(definedKeys); + } + + public Map> getInterpolationOccurences() { + return Collections.unmodifiableMap(interpolationOccurences); + } + + public Map> getKeyDefinitions() { + return Collections.unmodifiableMap(keyDefintions); + } + + protected final FileObject getFileObject() { + return getSnapshot().getSource().getFileObject(); + } + + private class BadgingDefaultError extends DefaultError implements Error.Badging { + + private boolean badging; + + public BadgingDefaultError(String key, String displayName, String description, FileObject file, int start, int end, Severity severity, boolean badging) { + super(key, displayName, description, file, start, end, severity); + this.badging = badging; + } + + @Override + public boolean showExplorerBadge() { + return badging; + } + + + } +} diff --git a/ide/languages.env/src/org/netbeans/modules/languages/env/project/EnvFileImpl.java b/ide/languages.env/src/org/netbeans/modules/languages/env/project/EnvFileImpl.java new file mode 100644 index 000000000000..8c1c6dc16fbf --- /dev/null +++ b/ide/languages.env/src/org/netbeans/modules/languages/env/project/EnvFileImpl.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.languages.env.project; + +import java.util.Collection; +import javax.swing.event.ChangeListener; +import org.netbeans.api.project.Project; +import org.netbeans.modules.web.common.spi.ImportantFilesImplementation; +import org.netbeans.modules.web.common.spi.ImportantFilesSupport; +import org.netbeans.spi.project.LookupProvider; +import org.netbeans.spi.project.ProjectServiceProvider; + +@ProjectServiceProvider(service = ImportantFilesImplementation.class, projectTypes = { + @LookupProvider.Registration.ProjectType(id = "org-netbeans-modules-web-clientproject"), + @LookupProvider.Registration.ProjectType(id = "org-netbeans-modules-php-project"), +}) +public class EnvFileImpl implements ImportantFilesImplementation { + + private final ImportantFilesSupport support; + + public EnvFileImpl(Project project) { + assert project != null; + support = ImportantFilesSupport.create(project.getProjectDirectory(), ".env"); // NOI18N + } + + @Override + public Collection getFiles() { + return support.getFiles(null); + } + + @Override + public void addChangeListener(ChangeListener listener) { + support.addChangeListener(listener); + } + + @Override + public void removeChangeListener(ChangeListener listener) { + support.removeChangeListener(listener); + } + +} diff --git a/ide/languages.env/src/org/netbeans/modules/languages/env/resources/Bundle.properties b/ide/languages.env/src/org/netbeans/modules/languages/env/resources/Bundle.properties new file mode 100644 index 000000000000..58683ba01a8e --- /dev/null +++ b/ide/languages.env/src/org/netbeans/modules/languages/env/resources/Bundle.properties @@ -0,0 +1,37 @@ +# 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-Name=Env Files Editor Support +OpenIDE-Module-Display-Category=Editing +OpenIDE-Module-Short-Description=Support for editing Env files. +OpenIDE-Module-Long-Description=Support for editing Env files. + +Editors/text/x-env=Env + +text/x-env=Env + +comment=Comment +string=String +keyword=Keyword (null, true ...) +operator=Operator +delimitator=Value delimitator (',', '|', ':') +dollar=Dollar +interpolation_operator=Interpolation operator +key=Key +value=Value +whitespace=Whitespace +error=Error \ No newline at end of file diff --git a/ide/languages.env/src/org/netbeans/modules/languages/env/resources/FontAndColors-bluetheme.xml b/ide/languages.env/src/org/netbeans/modules/languages/env/resources/FontAndColors-bluetheme.xml new file mode 100644 index 000000000000..5287f15c36cb --- /dev/null +++ b/ide/languages.env/src/org/netbeans/modules/languages/env/resources/FontAndColors-bluetheme.xml @@ -0,0 +1,25 @@ + + + + + + \ No newline at end of file diff --git a/ide/languages.env/src/org/netbeans/modules/languages/env/resources/FontAndColors-citylights.xml b/ide/languages.env/src/org/netbeans/modules/languages/env/resources/FontAndColors-citylights.xml new file mode 100644 index 000000000000..5287f15c36cb --- /dev/null +++ b/ide/languages.env/src/org/netbeans/modules/languages/env/resources/FontAndColors-citylights.xml @@ -0,0 +1,25 @@ + + + + + + \ No newline at end of file diff --git a/ide/languages.env/src/org/netbeans/modules/languages/env/resources/FontAndColors.xml b/ide/languages.env/src/org/netbeans/modules/languages/env/resources/FontAndColors.xml new file mode 100644 index 000000000000..aec59c5f56d3 --- /dev/null +++ b/ide/languages.env/src/org/netbeans/modules/languages/env/resources/FontAndColors.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ide/languages.env/src/org/netbeans/modules/languages/env/resources/TemplateHelp.html b/ide/languages.env/src/org/netbeans/modules/languages/env/resources/TemplateHelp.html new file mode 100644 index 000000000000..33ef012129d4 --- /dev/null +++ b/ide/languages.env/src/org/netbeans/modules/languages/env/resources/TemplateHelp.html @@ -0,0 +1,29 @@ + + + + + + + + Creates a new Env configuration file. You can edit the file in the IDE's Source Editor. To change this template, choose Tools | Template Manager and open the template in the editor. + + diff --git a/ide/languages.env/src/org/netbeans/modules/languages/env/resources/envColoring.env b/ide/languages.env/src/org/netbeans/modules/languages/env/resources/envColoring.env new file mode 100644 index 000000000000..9031e58f9801 --- /dev/null +++ b/ide/languages.env/src/org/netbeans/modules/languages/env/resources/envColoring.env @@ -0,0 +1,26 @@ +#COMMENT +KEY=VALUE +MY_OTHER_KEY=MY_OTHER_VALUE +KEY_STR = "Multiline +text" + +SINGLE_QUOTED_TEXT='single quoted' +BACKTICK_QUOTED_TEXT=`backtick quoted text` + +INTERPOLATED_KEY = "${KEY_STR}" + +INTERPOLATED_NAME_DEF1="${VAR:-default}" +INTERPOLATED_NAME_DEF2="${VAR-default}" + +INTERPOLATED_NAME_ERR1="${VAR:?error}" + +ENUMERATION=value1, value2, value3 + +URL_PATH =https://my.demo.com + +PATH=/my/path + +# +KEYWORD=true +KEYWORD_NULL=true +NO_VALUE=?? diff --git a/ide/languages.env/src/org/netbeans/modules/languages/env/resources/envFile.env b/ide/languages.env/src/org/netbeans/modules/languages/env/resources/envFile.env new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/ide/languages.env/src/org/netbeans/modules/languages/env/resources/env_file_16.png b/ide/languages.env/src/org/netbeans/modules/languages/env/resources/env_file_16.png new file mode 100644 index 0000000000000000000000000000000000000000..837aeaa20ea0dbf175f34cd4a5bb39c29d3365d4 GIT binary patch literal 480 zcmV<60U!Q}P)be#F3HsESw`tB@EpF-REr8;lYI8(|O$Vyg%?A|^o$<=?QH zY?!D>)8@6_{b6|R^IqO-()cDP_vGAjKlz@MD*(0*&nzuujX_9>H3lig(*EY^L#%1@ z;KaU{s)_OP_F*q?9<9>GUR)RL#@^{-_^kvm#_(17NsP~OjoOdL$9I*5xmiZCL&c4~ zY7OXc z9Hyrxsn=Wpy3*a;-rYxcptT~GDUiz)FxCcnF4IqEI?e3V4cXB#=JO8>3=V>@aR9*c z>w0kE=esdSeZ4&xW1;}1RHFSPrKE`T`wkj~@!_Mk06GTHIyOQpb);m_A9K)-oK%v7 z#!_03u+zBP@g0Pg=z#wdEADnsZ03Kb0=5AK2ux@MA7C`gD{F0!5mxj2P5k5S6TmOS WG|DYuil3YS0000F4Kqr literal 0 HcmV?d00001 diff --git a/ide/languages.env/src/org/netbeans/modules/languages/env/resources/layer.xml b/ide/languages.env/src/org/netbeans/modules/languages/env/resources/layer.xml new file mode 100644 index 000000000000..4225206f124b --- /dev/null +++ b/ide/languages.env/src/org/netbeans/modules/languages/env/resources/layer.xml @@ -0,0 +1,103 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ide/languages.env/src/org/netbeans/modules/languages/env/resources/package-info.java b/ide/languages.env/src/org/netbeans/modules/languages/env/resources/package-info.java new file mode 100644 index 000000000000..0cef6cc7c27e --- /dev/null +++ b/ide/languages.env/src/org/netbeans/modules/languages/env/resources/package-info.java @@ -0,0 +1,29 @@ +/* + * 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. + */ + +@TemplateRegistration(folder = "Other", + displayName = "Env file", + content = "envFile.env", + position = 671, + category = "simple-files", + description = "TemplateHelp.html" +) +package org.netbeans.modules.languages.env.resources; + +import org.netbeans.api.templates.TemplateRegistration; diff --git a/ide/languages.env/test/unit/data/testfiles/antlr4/lexer/env01.env b/ide/languages.env/test/unit/data/testfiles/antlr4/lexer/env01.env new file mode 100644 index 000000000000..fa6590d2601e --- /dev/null +++ b/ide/languages.env/test/unit/data/testfiles/antlr4/lexer/env01.env @@ -0,0 +1,27 @@ +APP_NAME='my file' +TEXT_VALUE="Lorem impsum +ipsum lorem. +" + +NO_VALUE= + +INTERPOLATED_NAME="${APP_NAME}" + +INTERPOLATED_NAME_DEF1="${VAR:-default}" +INTERPOLATED_NAME_DEF2="${VAR-default}" + +INTERPOLATED_NAME_ERR1="${VAR:?error}" +INTERPOLATED_NAME_ERR2="${VAR?error}" + +INTERPOLATED_NAME_REPLACEMENT1="${VAR:+alternate}" +INTERPOLATED_NAME_REPLACEMENT2="${VAR+alternate}" + +# With spaces and comments +DATABASE_URL = postgres://user:pass@host:port/db # Development DB + +# With interpolation +API_HOST=api.myproject.com + +# Using quotes for values with spaces or special chars +API_KEY="api_test_..." +MESSAGE='Hello, World!' diff --git a/ide/languages.env/test/unit/data/testfiles/antlr4/lexer/env01.env.lexer b/ide/languages.env/test/unit/data/testfiles/antlr4/lexer/env01.env.lexer new file mode 100644 index 000000000000..3e0400965ead --- /dev/null +++ b/ide/languages.env/test/unit/data/testfiles/antlr4/lexer/env01.env.lexer @@ -0,0 +1,84 @@ +Token #3 KEY [APP_NAME] +Token #5 ASSIGN_OPERATOR [=] +Token #1 NL [\n] +Token #3 KEY [TEXT_VALUE] +Token #5 ASSIGN_OPERATOR [=] +Token #1 NL [\n] +Token #1 NL [\n] +Token #3 KEY [NO_VALUE] +Token #5 ASSIGN_OPERATOR [=] +Token #1 NL [\n] +Token #1 NL [\n] +Token #3 KEY [INTERPOLATED_NAME] +Token #5 ASSIGN_OPERATOR [=] +Token #2 DOLLAR [$] +Token #20 '{' [{] +Token #3 KEY [APP_NAME] +Token #21 '}' [}] +Token #1 NL [\n] +Token #1 NL [\n] +Token #3 KEY [INTERPOLATED_NAME_DEF1] +Token #5 ASSIGN_OPERATOR [=] +Token #2 DOLLAR [$] +Token #20 '{' [{] +Token #3 KEY [VAR] +Token #22 INTERPOLATION_OPERATOR [:-] +Token #21 '}' [}] +Token #1 NL [\n] +Token #3 KEY [INTERPOLATED_NAME_DEF2] +Token #5 ASSIGN_OPERATOR [=] +Token #2 DOLLAR [$] +Token #20 '{' [{] +Token #3 KEY [VAR] +Token #22 INTERPOLATION_OPERATOR [-] +Token #21 '}' [}] +Token #1 NL [\n] +Token #1 NL [\n] +Token #3 KEY [INTERPOLATED_NAME_ERR1] +Token #5 ASSIGN_OPERATOR [=] +Token #2 DOLLAR [$] +Token #20 '{' [{] +Token #3 KEY [VAR] +Token #22 INTERPOLATION_OPERATOR [:?] +Token #21 '}' [}] +Token #1 NL [\n] +Token #3 KEY [INTERPOLATED_NAME_ERR2] +Token #5 ASSIGN_OPERATOR [=] +Token #2 DOLLAR [$] +Token #20 '{' [{] +Token #3 KEY [VAR] +Token #22 INTERPOLATION_OPERATOR [?] +Token #21 '}' [}] +Token #1 NL [\n] +Token #1 NL [\n] +Token #3 KEY [INTERPOLATED_NAME_REPLACEMENT1] +Token #5 ASSIGN_OPERATOR [=] +Token #2 DOLLAR [$] +Token #20 '{' [{] +Token #3 KEY [VAR] +Token #22 INTERPOLATION_OPERATOR [:+] +Token #21 '}' [}] +Token #1 NL [\n] +Token #3 KEY [INTERPOLATED_NAME_REPLACEMENT2] +Token #5 ASSIGN_OPERATOR [=] +Token #2 DOLLAR [$] +Token #20 '{' [{] +Token #3 KEY [VAR] +Token #22 INTERPOLATION_OPERATOR [+] +Token #21 '}' [}] +Token #1 NL [\n] +Token #1 NL [\n] +Token #3 KEY [DATABASE_URL] +Token #5 ASSIGN_OPERATOR [=] +Token #1 NL [\n] +Token #1 NL [\n] +Token #3 KEY [API_HOST] +Token #5 ASSIGN_OPERATOR [=] +Token #1 NL [\n] +Token #1 NL [\n] +Token #3 KEY [API_KEY] +Token #5 ASSIGN_OPERATOR [=] +Token #1 NL [\n] +Token #3 KEY [MESSAGE] +Token #5 ASSIGN_OPERATOR [=] +Token #1 NL [\n] diff --git a/ide/languages.env/test/unit/data/testfiles/lexer/env01.env b/ide/languages.env/test/unit/data/testfiles/lexer/env01.env new file mode 100644 index 000000000000..fa6590d2601e --- /dev/null +++ b/ide/languages.env/test/unit/data/testfiles/lexer/env01.env @@ -0,0 +1,27 @@ +APP_NAME='my file' +TEXT_VALUE="Lorem impsum +ipsum lorem. +" + +NO_VALUE= + +INTERPOLATED_NAME="${APP_NAME}" + +INTERPOLATED_NAME_DEF1="${VAR:-default}" +INTERPOLATED_NAME_DEF2="${VAR-default}" + +INTERPOLATED_NAME_ERR1="${VAR:?error}" +INTERPOLATED_NAME_ERR2="${VAR?error}" + +INTERPOLATED_NAME_REPLACEMENT1="${VAR:+alternate}" +INTERPOLATED_NAME_REPLACEMENT2="${VAR+alternate}" + +# With spaces and comments +DATABASE_URL = postgres://user:pass@host:port/db # Development DB + +# With interpolation +API_HOST=api.myproject.com + +# Using quotes for values with spaces or special chars +API_KEY="api_test_..." +MESSAGE='Hello, World!' diff --git a/ide/languages.env/test/unit/data/testfiles/lexer/env01.env.lexer b/ide/languages.env/test/unit/data/testfiles/lexer/env01.env.lexer new file mode 100644 index 000000000000..9752449f0cde --- /dev/null +++ b/ide/languages.env/test/unit/data/testfiles/lexer/env01.env.lexer @@ -0,0 +1,111 @@ +Token #0 KEY [APP_NAME] +Token #1 OPERATOR [=] +Token #2 STRING ['my file'] +Token #3 WS [\n] +Token #4 KEY [TEXT_VALUE] +Token #5 OPERATOR [=] +Token #6 STRING ["Lorem impsum\nipsum lorem.\n"] +Token #7 WS [\n\n] +Token #8 KEY [NO_VALUE] +Token #9 OPERATOR [=] +Token #10 WS [\n\n] +Token #11 KEY [INTERPOLATED_NAME] +Token #12 OPERATOR [=] +Token #13 STRING ["] +Token #14 DOLLAR [$] +Token #15 INTERPOLATION_DELIMITATOR [{] +Token #16 KEY [APP_NAME] +Token #17 INTERPOLATION_DELIMITATOR [}] +Token #18 STRING ["] +Token #19 WS [\n\n] +Token #20 KEY [INTERPOLATED_NAME_DEF1] +Token #21 OPERATOR [=] +Token #22 STRING ["] +Token #23 DOLLAR [$] +Token #24 INTERPOLATION_DELIMITATOR [{] +Token #25 KEY [VAR] +Token #26 INTERPOLATION_OPERATOR [:-] +Token #27 VALUE [default] +Token #28 INTERPOLATION_DELIMITATOR [}] +Token #29 STRING ["] +Token #30 WS [\n] +Token #31 KEY [INTERPOLATED_NAME_DEF2] +Token #32 OPERATOR [=] +Token #33 STRING ["] +Token #34 DOLLAR [$] +Token #35 INTERPOLATION_DELIMITATOR [{] +Token #36 KEY [VAR] +Token #37 INTERPOLATION_OPERATOR [-] +Token #38 VALUE [default] +Token #39 INTERPOLATION_DELIMITATOR [}] +Token #40 STRING ["] +Token #41 WS [\n\n] +Token #42 KEY [INTERPOLATED_NAME_ERR1] +Token #43 OPERATOR [=] +Token #44 STRING ["] +Token #45 DOLLAR [$] +Token #46 INTERPOLATION_DELIMITATOR [{] +Token #47 KEY [VAR] +Token #48 INTERPOLATION_OPERATOR [:] +Token #49 INTERPOLATION_OPERATOR [?] +Token #50 VALUE [error] +Token #51 INTERPOLATION_DELIMITATOR [}] +Token #52 STRING ["] +Token #53 WS [\n] +Token #54 KEY [INTERPOLATED_NAME_ERR2] +Token #55 OPERATOR [=] +Token #56 STRING ["] +Token #57 DOLLAR [$] +Token #58 INTERPOLATION_DELIMITATOR [{] +Token #59 KEY [VAR] +Token #60 INTERPOLATION_OPERATOR [?] +Token #61 VALUE [error] +Token #62 INTERPOLATION_DELIMITATOR [}] +Token #63 STRING ["] +Token #64 WS [\n\n] +Token #65 KEY [INTERPOLATED_NAME_REPLACEMENT1] +Token #66 OPERATOR [=] +Token #67 STRING ["] +Token #68 DOLLAR [$] +Token #69 INTERPOLATION_DELIMITATOR [{] +Token #70 KEY [VAR] +Token #71 INTERPOLATION_OPERATOR [:+] +Token #72 VALUE [alternate] +Token #73 INTERPOLATION_DELIMITATOR [}] +Token #74 STRING ["] +Token #75 WS [\n] +Token #76 KEY [INTERPOLATED_NAME_REPLACEMENT2] +Token #77 OPERATOR [=] +Token #78 STRING ["] +Token #79 DOLLAR [$] +Token #80 INTERPOLATION_DELIMITATOR [{] +Token #81 KEY [VAR] +Token #82 INTERPOLATION_OPERATOR [+] +Token #83 VALUE [alternate] +Token #84 INTERPOLATION_DELIMITATOR [}] +Token #85 STRING ["] +Token #86 WS [\n\n] +Token #87 COMMENT [# With spaces and comments\n] +Token #88 KEY [DATABASE_URL] +Token #89 OPERATOR [ =] +Token #90 VALUE [ postgres] +Token #91 DELIMITATOR [:] +Token #92 VALUE [//user] +Token #93 DELIMITATOR [:] +Token #94 VALUE [pass@host] +Token #95 DELIMITATOR [:] +Token #96 VALUE [port/db] +Token #97 COMMENT [ # Development DB\n\n# With interpolation\n] +Token #98 KEY [API_HOST] +Token #99 OPERATOR [=] +Token #100 VALUE [api.myproject.com] +Token #101 WS [\n\n] +Token #102 COMMENT [# Using quotes for values with spaces or special chars\n] +Token #103 KEY [API_KEY] +Token #104 OPERATOR [=] +Token #105 STRING ["api_test_..."] +Token #106 WS [\n] +Token #107 KEY [MESSAGE] +Token #108 OPERATOR [=] +Token #109 STRING ['Hello, World!'] +Token #110 WS [\n] diff --git a/ide/languages.env/test/unit/src/org/netbeans/modules/languages/env/EnvTestBase.java b/ide/languages.env/test/unit/src/org/netbeans/modules/languages/env/EnvTestBase.java new file mode 100644 index 000000000000..5533db475fb7 --- /dev/null +++ b/ide/languages.env/test/unit/src/org/netbeans/modules/languages/env/EnvTestBase.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.languages.env; + +import org.netbeans.lib.lexer.test.TestLanguageProvider; +import org.netbeans.modules.csl.api.test.CslTestBase; +import org.netbeans.modules.csl.spi.DefaultLanguageConfig; +import org.openide.util.lookup.Lookups; +import org.openide.util.test.MockLookup; + +public abstract class EnvTestBase extends CslTestBase { + + public EnvTestBase(String name) { + super(name); + MockLookup.setLookup(Lookups.singleton(new TestLanguageProvider())); + } + + @Override + public void setUp() throws Exception { + MockLookup.init(); + MockLookup.setInstances( + new TestLanguageProvider()); + super.setUp(); + } + + @Override + protected DefaultLanguageConfig getPreferredLanguage() { + return new EnvLanguage(); + } + + @Override + protected String getPreferredMimeType() { + return EnvFileResolver.MIME_TYPE; + } +} diff --git a/ide/languages.env/test/unit/src/org/netbeans/modules/languages/env/EnvTestUtils.java b/ide/languages.env/test/unit/src/org/netbeans/modules/languages/env/EnvTestUtils.java new file mode 100644 index 000000000000..15ccc22e7f51 --- /dev/null +++ b/ide/languages.env/test/unit/src/org/netbeans/modules/languages/env/EnvTestUtils.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.languages.env; + +public class EnvTestUtils { + + private EnvTestUtils() { + + } + + 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/ide/languages.env/test/unit/src/org/netbeans/modules/languages/env/grammar/antlr4/lexer/EnvAntlrLexerTestBase.java b/ide/languages.env/test/unit/src/org/netbeans/modules/languages/env/grammar/antlr4/lexer/EnvAntlrLexerTestBase.java new file mode 100644 index 000000000000..56fb05137197 --- /dev/null +++ b/ide/languages.env/test/unit/src/org/netbeans/modules/languages/env/grammar/antlr4/lexer/EnvAntlrLexerTestBase.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.languages.env.grammar.antlr4.lexer; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import org.antlr.v4.runtime.CharStream; +import org.antlr.v4.runtime.CharStreams; +import org.antlr.v4.runtime.Token; +import org.antlr.v4.runtime.Vocabulary; +import org.netbeans.modules.languages.env.EnvTestBase; +import org.netbeans.modules.languages.env.EnvTestUtils; +import org.netbeans.modules.languages.env.grammar.antlr4.parser.EnvAntlrLexer; + +public abstract class EnvAntlrLexerTestBase extends EnvTestBase { + + public EnvAntlrLexerTestBase(String name) { + super(name); + } + + public void checkLexer(final String filePath) throws Exception { + File testFile = new File(getDataDir(), filePath); + BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(testFile), StandardCharsets.UTF_8)); + + CharStream stream = CharStreams.fromReader(reader); + EnvAntlrLexer lexer = new EnvAntlrLexer(stream); + StringBuilder result = new StringBuilder(); + Vocabulary vocabulary = lexer.getVocabulary(); + for (Token token = lexer.nextToken(); token != null && token.getType() != Token.EOF; token = lexer.nextToken()) { + int type = token.getType(); + result.append("Token #"); + result.append(type); + result.append(" "); + result.append(vocabulary.getDisplayName(type)); + + String content = EnvTestUtils.replaceLinesAndTabs(token.getText()); + if (!content.isEmpty()) { + result.append(" "); + result.append("["); + result.append(content); + result.append("]"); + } + result.append("\n"); + } + + assertDescriptionMatches(filePath, result.toString(), false, ".lexer"); + } +} diff --git a/ide/languages.env/test/unit/src/org/netbeans/modules/languages/env/grammar/antlr4/lexer/EnvLexerTest.java b/ide/languages.env/test/unit/src/org/netbeans/modules/languages/env/grammar/antlr4/lexer/EnvLexerTest.java new file mode 100644 index 000000000000..a60049f5c8db --- /dev/null +++ b/ide/languages.env/test/unit/src/org/netbeans/modules/languages/env/grammar/antlr4/lexer/EnvLexerTest.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.languages.env.grammar.antlr4.lexer; + +public class EnvLexerTest extends EnvAntlrLexerTestBase { + + public EnvLexerTest(String name) { + super(name); + } + + public void testEnv01() throws Exception { + checkLexer("testfiles/antlr4/lexer/env01.env"); + } +} diff --git a/ide/languages.env/test/unit/src/org/netbeans/modules/languages/env/lexer/EnvLexerTest.java b/ide/languages.env/test/unit/src/org/netbeans/modules/languages/env/lexer/EnvLexerTest.java new file mode 100644 index 000000000000..1ed6036b5b8e --- /dev/null +++ b/ide/languages.env/test/unit/src/org/netbeans/modules/languages/env/lexer/EnvLexerTest.java @@ -0,0 +1,76 @@ +/* + * 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.languages.env.lexer; + +import java.io.File; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import static junit.framework.TestCase.assertNotNull; +import org.netbeans.api.lexer.Language; +import org.netbeans.api.lexer.TokenHierarchy; +import org.netbeans.api.lexer.TokenSequence; +import org.netbeans.lib.lexer.test.LexerTestUtilities; +import org.netbeans.modules.languages.env.EnvLanguage; +import org.netbeans.modules.languages.env.EnvTestBase; +import org.netbeans.modules.languages.env.EnvTestUtils; + +public class EnvLexerTest extends EnvTestBase { + + public EnvLexerTest(String name) { + super(name); + } + + @Override + public void setUp() throws Exception { + LexerTestUtilities.setTesting(true); + } + + public void testLexer_01() throws Exception { + checkLexer("testfiles/lexer/env01.env"); + } + + private void checkLexer(final String filePath) throws Exception { + String fileContent = Files.readString(new File(getDataDir(), filePath).toPath(), StandardCharsets.UTF_8); + EnvLanguage langSettings = new EnvLanguage(); + Language language; + language = langSettings.getLexerLanguage(); + TokenHierarchy th = TokenHierarchy.create(fileContent, language); + TokenSequence ts = th.tokenSequence(language); + assertNotNull("Can not obtain token sequence for file: " + filePath, ts); + StringBuilder result = new StringBuilder(); + while (ts.moveNext()) { + EnvTokenId tokenId = ts.token().id(); + CharSequence tokenText = ts.token().text(); + result.append("Token #"); + result.append(ts.index()); + result.append(" "); + result.append(tokenId.name()); + String token = EnvTestUtils.replaceLinesAndTabs(tokenText.toString()); + if (!token.isEmpty()) { + result.append(" "); + result.append("["); + result.append(token); + result.append("]"); + } + result.append("\n"); + } + + assertDescriptionMatches(filePath, result.toString(), false, ".lexer"); + } +} diff --git a/ide/web.common/nbproject/project.xml b/ide/web.common/nbproject/project.xml index ef5d687077d1..ff37fe6b55b7 100644 --- a/ide/web.common/nbproject/project.xml +++ b/ide/web.common/nbproject/project.xml @@ -237,6 +237,7 @@ org.netbeans.modules.javascript2.nodejs org.netbeans.modules.javascript2.requirejs org.netbeans.modules.languages.apacheconf + org.netbeans.modules.languages.env org.netbeans.modules.languages.ini org.netbeans.modules.languages.neon org.netbeans.modules.maven diff --git a/nbbuild/cluster.properties b/nbbuild/cluster.properties index 58775c1762c0..f4f3795bbc01 100644 --- a/nbbuild/cluster.properties +++ b/nbbuild/cluster.properties @@ -373,6 +373,7 @@ nb.cluster.ide=\ jumpto,\ languages,\ languages.diff,\ + languages.env,\ languages.go,\ languages.hcl,\ languages.jflex,\