This is an automated email from the ASF dual-hosted git repository. dbalek pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/netbeans.git
The following commit(s) were added to refs/heads/master by this push: new c73b134 Micronaut data finder methods code completion (#3333) c73b134 is described below commit c73b134cf520288a4ee5101885fb4039253ec496 Author: Dusan Balek <dusan.ba...@oracle.com> AuthorDate: Mon Nov 29 16:48:37 2021 +0100 Micronaut data finder methods code completion (#3333) --- enterprise/micronaut/nbproject/project.xml | 11 +- .../MicronautConfigCompletionCollector.java | 4 +- .../completion/MicronautConfigCompletionItem.java | 329 ---------------- .../MicronautConfigCompletionProvider.java | 162 +++++++- .../MicronautDataCompletionCollector.java | 53 +++ .../MicronautDataCompletionProvider.java | 103 +++++ .../completion/MicronautDataCompletionTask.java | 434 +++++++++++++++++++++ .../newproject/MicronautProjectWizardIterator.java | 8 +- .../refactor/MicronautRefactoringFactory.java | 4 +- ide/editor.completion/apichanges.xml | 15 + ide/editor.completion/nbproject/project.properties | 2 +- .../CompletionSupportSpiPackageAccessor.java | 41 ++ .../editor/completion/SimpleCompletionItem.java | 172 ++++++++ .../completion/support/CompletionUtilities.java | 228 ++++++++++- java/java.lsp.server/vscode/src/extension.ts | 2 +- 15 files changed, 1222 insertions(+), 346 deletions(-) diff --git a/enterprise/micronaut/nbproject/project.xml b/enterprise/micronaut/nbproject/project.xml index b307a94..61dc5d8 100644 --- a/enterprise/micronaut/nbproject/project.xml +++ b/enterprise/micronaut/nbproject/project.xml @@ -117,7 +117,7 @@ <compile-dependency/> <run-dependency> <release-version>1</release-version> - <specification-version>1.56</specification-version> + <specification-version>1.60</specification-version> </run-dependency> </dependency> <dependency> @@ -165,6 +165,15 @@ </run-dependency> </dependency> <dependency> + <code-name-base>org.netbeans.modules.java.lexer</code-name-base> + <build-prerequisite/> + <compile-dependency/> + <run-dependency> + <release-version>1</release-version> + <specification-version>1.50</specification-version> + </run-dependency> + </dependency> + <dependency> <code-name-base>org.netbeans.modules.java.project</code-name-base> <build-prerequisite/> <compile-dependency/> diff --git a/enterprise/micronaut/src/org/netbeans/modules/micronaut/completion/MicronautConfigCompletionCollector.java b/enterprise/micronaut/src/org/netbeans/modules/micronaut/completion/MicronautConfigCompletionCollector.java index 8a96082..06d40ba 100644 --- a/enterprise/micronaut/src/org/netbeans/modules/micronaut/completion/MicronautConfigCompletionCollector.java +++ b/enterprise/micronaut/src/org/netbeans/modules/micronaut/completion/MicronautConfigCompletionCollector.java @@ -77,7 +77,7 @@ public class MicronautConfigCompletionCollector implements CompletionCollector { ArrayUtilities.appendSpaces(insertText, indentLevelSize); } } - return CompletionCollector.newBuilder(propName).kind(Completion.Kind.Property).sortText(String.format("%4d%s", 10, propName)) + return CompletionCollector.newBuilder(propName).kind(Completion.Kind.Property).sortText(String.format("%04d%s", 10, propName)) .insertText(insertText.toString()).insertTextFormat(insertTextFormat).build(); } @@ -112,7 +112,7 @@ public class MicronautConfigCompletionCollector implements CompletionCollector { } } CompletionCollector.Builder builder = CompletionCollector.newBuilder(property.getId()).kind(Completion.Kind.Property) - .sortText(String.format("%4d%s", property.isDeprecated() ? 30 : 20, property.getId())).insertText(insertText.toString()) + .sortText(String.format("%04d%s", property.isDeprecated() ? 30 : 20, property.getId())).insertText(insertText.toString()) .insertTextFormat(insertTextFormat).documentation(new MicronautConfigDocumentation(property).getText()); if (property.isDeprecated()) { builder.addTag(Completion.Tag.Deprecated); diff --git a/enterprise/micronaut/src/org/netbeans/modules/micronaut/completion/MicronautConfigCompletionItem.java b/enterprise/micronaut/src/org/netbeans/modules/micronaut/completion/MicronautConfigCompletionItem.java deleted file mode 100644 index 1a4e71d..0000000 --- a/enterprise/micronaut/src/org/netbeans/modules/micronaut/completion/MicronautConfigCompletionItem.java +++ /dev/null @@ -1,329 +0,0 @@ -/* - * 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.micronaut.completion; - -import java.awt.Color; -import java.awt.Font; -import java.awt.Graphics; -import java.awt.event.InputEvent; -import java.awt.event.KeyEvent; -import java.util.regex.Pattern; -import javax.swing.ImageIcon; -import javax.swing.text.Document; -import javax.swing.text.JTextComponent; -import org.netbeans.api.editor.completion.Completion; -import org.netbeans.api.editor.document.LineDocument; -import org.netbeans.api.editor.document.LineDocumentUtils; -import org.netbeans.lib.editor.codetemplates.api.CodeTemplateManager; -import org.netbeans.lib.editor.util.ArrayUtilities; -import org.netbeans.modules.editor.indent.api.IndentUtils; -import org.netbeans.spi.editor.completion.CompletionItem; -import org.netbeans.spi.editor.completion.CompletionTask; -import org.netbeans.spi.editor.completion.support.CompletionUtilities; -import org.netbeans.swing.plaf.LFCustoms; -import org.openide.util.Exceptions; -import org.openide.util.ImageUtilities; -import org.openide.xml.XMLUtil; -import org.springframework.boot.configurationmetadata.ConfigurationMetadataProperty; - -/** - * - * @author Dusan Balek - */ -public abstract class MicronautConfigCompletionItem implements CompletionItem { - - public static final String PROPERTY_NAME_COLOR = getHTMLColor(64, 64, 217); - private static final String ICON = "org/netbeans/modules/editor/resources/completion/field_16.png"; //NOI18N - - public static MicronautConfigCompletionItem createTopLevelPropertyItem(String propName, int offset, int baseIndent, int indentLevelSize) { - return new TopLevelItem(propName, offset, baseIndent, indentLevelSize); - } - - public static MicronautConfigCompletionItem createPropertyItem(ConfigurationMetadataProperty property, int offset, int baseIndent, int indentLevelSize, int idx) { - return new PropertyItem(property, offset, baseIndent, indentLevelSize, idx); - } - - protected final String propName; - protected final int offset; - protected final int baseIndent; - protected final int indentLevelSize; - private ImageIcon icon; - - private MicronautConfigCompletionItem(String propName, int offset, int baseIndent, int indentLevelSize) { - this.propName = propName; - this.offset = offset; - this.baseIndent = baseIndent; - this.indentLevelSize = indentLevelSize; - } - - @Override - public void defaultAction(JTextComponent component) { - if (component != null) { - Completion.get().hideDocumentation(); - Completion.get().hideCompletion(); - process(component, false); - } - } - - @Override - public void processKeyEvent(KeyEvent evt) { - if (evt.getID() == KeyEvent.KEY_PRESSED && evt.getKeyCode() == KeyEvent.VK_ENTER && (evt.getModifiers() & InputEvent.CTRL_MASK) > 0) { - JTextComponent component = (JTextComponent)evt.getSource(); - Completion.get().hideDocumentation(); - Completion.get().hideCompletion(); - process(component, true); - evt.consume(); - } - } - - @Override - public int getPreferredWidth(Graphics g, Font defaultFont) { - return CompletionUtilities.getPreferredWidth(getLeftHtmlText(), getRightHtmlText(), g, defaultFont); - } - - @Override - public void render(Graphics g, Font defaultFont, Color defaultColor, Color backgroundColor, int width, int height, boolean selected) { - CompletionUtilities.renderHtml(getIcon(), getLeftHtmlText(), getRightHtmlText(), g, defaultFont, defaultColor, width, height, selected); - } - - @Override - public CompletionTask createDocumentationTask() { - return null; - } - - @Override - public CompletionTask createToolTipTask() { - return null; - } - - @Override - public boolean instantSubstitution(JTextComponent component) { - return false; - } - - @Override - public CharSequence getSortText() { - return propName; - } - - @Override - public CharSequence getInsertPrefix() { - return propName; - } - - protected String getLeftHtmlText() { - return null; - } - - protected String getRightHtmlText() { - return null; - } - - protected ImageIcon getIcon() { - if (icon == null) { - icon = ImageUtilities.loadImageIcon(ICON, false); - } - return icon; - } - - protected abstract void process(JTextComponent component, boolean overwrite); - - private static final class TopLevelItem extends MicronautConfigCompletionItem { - - private TopLevelItem(String propName, int offset, int baseIndent, int indentLevelSize) { - super(propName, offset, baseIndent, indentLevelSize); - } - - @Override - public int getSortPriority() { - return 10; - } - - @Override - protected String getLeftHtmlText() { - return PROPERTY_NAME_COLOR + "<b>" + propName + "</b></font>"; - } - - @Override - protected void process(JTextComponent component, boolean overwrite) { - try { - Document doc = component.getDocument(); - LineDocument lineDocument = LineDocumentUtils.as(doc, LineDocument.class); - if (lineDocument != null) { - int caretOffset = component.getCaretPosition(); - int end = LineDocumentUtils.getWordEnd(lineDocument, caretOffset); - if (overwrite && LineDocumentUtils.getWordStart(lineDocument, end) == offset) { - String textEnd = doc.getText(end, 1); - if (baseIndent < 0 && textEnd.endsWith(".") || textEnd.endsWith(":")) { - end++; - } - doc.remove(offset, Math.max(caretOffset, end) - offset); - } else if (offset < caretOffset) { - doc.remove(offset, caretOffset - offset); - } - StringBuilder sb = new StringBuilder(); - if (baseIndent < 0) { - sb.append("*".equals(propName) ? "${PAR#1 default=\"\"}" : propName).append(".${cursor completionInvoke}"); - } else { - int lineStart = LineDocumentUtils.getLineStart(lineDocument, caretOffset); - int lineIndent = IndentUtils.lineIndent(doc, lineStart); - ArrayUtilities.appendSpaces(sb, baseIndent - lineIndent); - sb.append("*".equals(propName) ? "${PAR#1 default=\"\"}" : propName).append(":\n"); - ArrayUtilities.appendSpaces(sb, baseIndent + indentLevelSize); - sb.append("${cursor completionInvoke}"); - } - CodeTemplateManager.get(doc).createTemporary(sb.toString()).insert(component); - } - } catch (Exception ex) { - Exceptions.printStackTrace(ex); - } - } - } - - private static final class PropertyItem extends MicronautConfigCompletionItem { - - private static final Pattern FQN = Pattern.compile("(\\w+\\.)+(\\w+)"); - private final ConfigurationMetadataProperty property; - private final int idx; - private String left; - private String right; - - private PropertyItem(ConfigurationMetadataProperty property, int offset, int baseIndent, int indentLevelSize, int idx) { - super(property.getId(), offset, baseIndent, indentLevelSize); - this.property = property; - this.idx = idx; - } - - @Override - public CompletionTask createDocumentationTask() { - return MicronautConfigCompletionProvider.createDocTask(property); - } - - @Override - public int getSortPriority() { - return property.isDeprecated() ? 30 : 20; - } - - @Override - protected String getLeftHtmlText() { - if (left == null) { - if (property.isDeprecated()) { - left = PROPERTY_NAME_COLOR + "<s>" + propName + "</s></font>"; - } else { - left = PROPERTY_NAME_COLOR + propName + "</font>"; - } - } - return left; - } - - @Override - protected String getRightHtmlText() { - if (right == null) { - String type = property.getType(); - right = type != null ? escape(FQN.matcher(type).replaceAll("$2")) : ""; - } - return right; - } - - @Override - protected void process(JTextComponent component, boolean overwrite) { - try { - Document doc = component.getDocument(); - LineDocument lineDocument = LineDocumentUtils.as(doc, LineDocument.class); - if (lineDocument != null) { - int caretOffset = component.getCaretPosition(); - int end = LineDocumentUtils.getWordEnd(lineDocument, caretOffset); - if (overwrite && LineDocumentUtils.getWordStart(lineDocument, end) == offset) { - String textEnd = doc.getText(end, 1); - while(baseIndent < 0 && textEnd.endsWith(".")) { - end = LineDocumentUtils.getWordEnd(lineDocument, end + 1); - textEnd = doc.getText(end, 1); - } - if (baseIndent < 0 && textEnd.endsWith("=") || textEnd.endsWith(":")) { - end++; - } - doc.remove(offset, Math.max(caretOffset, end) - offset); - } else if (offset < caretOffset) { - doc.remove(offset, caretOffset - offset); - } - StringBuilder sb = new StringBuilder(); - String name = propName.substring(idx); - String[] parts = name.split("\\."); - if (baseIndent < 0) { - int num = 1; - for (int i = 0; i < parts.length; i++) { - String part = parts[i]; - if ("*".equals(part)) { - sb.append("${PAR#" + num++ + " default=\"\"}"); - } else { - sb.append(part); - } - if (i < parts.length - 1) { - sb.append("."); - } else { - sb.append("=${cursor}"); - } - } - } else { - int lineStart = LineDocumentUtils.getLineStart(lineDocument, caretOffset); - int lineIndent = IndentUtils.lineIndent(doc, lineStart); - ArrayUtilities.appendSpaces(sb, baseIndent - lineIndent); - int indent = baseIndent; - int num = 1; - for (int i = 0; i < parts.length; i++) { - String part = parts[i]; - if ("*".equals(part)) { - sb.append("${PAR#" + num++ + " default=\"\"}"); - } else { - sb.append(part); - } - if (i < parts.length - 1) { - sb.append(":\n"); - ArrayUtilities.appendSpaces(sb, (indent = indent + indentLevelSize)); - } else { - sb.append(": ${cursor}"); - } - } - } - CodeTemplateManager.get(doc).createTemporary(sb.toString()).insert(component); - } - } catch (Exception ex) { - Exceptions.printStackTrace(ex); - } - } - } - - private static String getHTMLColor(int r, int g, int b) { - Color c = LFCustoms.shiftColor(new Color(r, g, b)); - return "<font color=#" //NOI18N - + LFCustoms.getHexString(c.getRed()) - + LFCustoms.getHexString(c.getGreen()) - + LFCustoms.getHexString(c.getBlue()) - + ">"; //NOI18N - } - - private static String escape(String s) { - if (s != null) { - try { - return XMLUtil.toAttributeValue(s); - } catch (Exception ex) {} - } - return s; - } -} diff --git a/enterprise/micronaut/src/org/netbeans/modules/micronaut/completion/MicronautConfigCompletionProvider.java b/enterprise/micronaut/src/org/netbeans/modules/micronaut/completion/MicronautConfigCompletionProvider.java index d4f6f02..92456a7 100644 --- a/enterprise/micronaut/src/org/netbeans/modules/micronaut/completion/MicronautConfigCompletionProvider.java +++ b/enterprise/micronaut/src/org/netbeans/modules/micronaut/completion/MicronautConfigCompletionProvider.java @@ -18,20 +18,33 @@ */ package org.netbeans.modules.micronaut.completion; +import java.awt.Color; +import java.util.regex.Pattern; +import javax.swing.text.BadLocationException; import javax.swing.text.Document; import javax.swing.text.JTextComponent; import org.netbeans.api.editor.document.EditorDocumentUtils; +import org.netbeans.api.editor.document.LineDocument; +import org.netbeans.api.editor.document.LineDocumentUtils; import org.netbeans.api.editor.mimelookup.MimeRegistration; import org.netbeans.api.project.FileOwnerQuery; import org.netbeans.api.project.Project; +import org.netbeans.lib.editor.codetemplates.api.CodeTemplateManager; +import org.netbeans.lib.editor.util.ArrayUtilities; +import org.netbeans.modules.editor.indent.api.IndentUtils; import org.netbeans.modules.micronaut.MicronautConfigProperties; import org.netbeans.modules.micronaut.MicronautConfigUtilities; +import org.netbeans.spi.editor.completion.CompletionItem; import org.netbeans.spi.editor.completion.CompletionProvider; import org.netbeans.spi.editor.completion.CompletionResultSet; import org.netbeans.spi.editor.completion.CompletionTask; import org.netbeans.spi.editor.completion.support.AsyncCompletionQuery; import org.netbeans.spi.editor.completion.support.AsyncCompletionTask; +import org.netbeans.spi.editor.completion.support.CompletionUtilities; +import org.netbeans.swing.plaf.LFCustoms; import org.openide.filesystems.FileObject; +import org.openide.util.Exceptions; +import org.openide.xml.XMLUtil; import org.springframework.boot.configurationmetadata.ConfigurationMetadataProperty; /** @@ -40,6 +53,8 @@ import org.springframework.boot.configurationmetadata.ConfigurationMetadataPrope */ public class MicronautConfigCompletionProvider implements CompletionProvider { + public static final String PROPERTY_NAME_COLOR = getHTMLColor(64, 64, 217); + @MimeRegistration(mimeType = "text/x-yaml", service = CompletionProvider.class) public static MicronautConfigCompletionProvider createYamlProvider() { return new MicronautConfigCompletionProvider(); @@ -75,12 +90,29 @@ public class MicronautConfigCompletionProvider implements CompletionProvider { return 0; } - static CompletionTask createDocTask(ConfigurationMetadataProperty element) { - return new AsyncCompletionTask(new MicronautConfigDocumentationQuery(element)); + private static String getHTMLColor(int r, int g, int b) { + Color c = LFCustoms.shiftColor(new Color(r, g, b)); + return "<font color=#" //NOI18N + + LFCustoms.getHexString(c.getRed()) + + LFCustoms.getHexString(c.getGreen()) + + LFCustoms.getHexString(c.getBlue()) + + ">"; //NOI18N + } + + private static String escape(String s) { + if (s != null) { + try { + return XMLUtil.toAttributeValue(s); + } catch (Exception ex) {} + } + return s; } private static class MicronautConfigCompletionQuery extends AsyncCompletionQuery { + private static final String ICON = "org/netbeans/modules/editor/resources/completion/field_16.png"; //NOI18N + private static final Pattern FQN = Pattern.compile("(\\w+\\.)+(\\w+)"); + private final Project project; public MicronautConfigCompletionQuery(Project project) { @@ -89,17 +121,133 @@ public class MicronautConfigCompletionProvider implements CompletionProvider { @Override protected void query(CompletionResultSet resultSet, Document doc, int caretOffset) { - resultSet.addAllItems(new MicronautConfigCompletionTask().query(doc, caretOffset, project, new MicronautConfigCompletionTask.ItemFactory<MicronautConfigCompletionItem>() { + resultSet.addAllItems(new MicronautConfigCompletionTask().query(doc, caretOffset, project, new MicronautConfigCompletionTask.ItemFactory<CompletionItem>() { @Override - public MicronautConfigCompletionItem createPropertyItem(ConfigurationMetadataProperty property, int offset, int baseIndent, int indentLevelSize, int idx) { + public CompletionItem createPropertyItem(ConfigurationMetadataProperty property, int offset, int baseIndent, int indentLevelSize, int idx) { resultSet.setAnchorOffset(offset); - return MicronautConfigCompletionItem.createPropertyItem(property, offset, baseIndent, indentLevelSize, idx); + String propName = property.getId(); + String propType = property.getType(); + CompletionUtilities.CompletionItemBuilder builder = CompletionUtilities.newCompletionItemBuilder(propName) + .iconResource(ICON) + .leftHtmlText(property.isDeprecated() + ? PROPERTY_NAME_COLOR + "<s>" + propName + "</s></font>" + : PROPERTY_NAME_COLOR + propName + "</font>") + .sortPriority(property.isDeprecated() ? 30 : 20) + .documentationTask(() -> { + return new AsyncCompletionTask(new MicronautConfigDocumentationQuery(property)); + }) + .onSelect(ctx -> { + try { + Document doc = ctx.getComponent().getDocument(); + LineDocument lineDocument = LineDocumentUtils.as(doc, LineDocument.class); + if (lineDocument != null) { + int caretOffset = ctx.getComponent().getCaretPosition(); + int end = LineDocumentUtils.getWordEnd(lineDocument, caretOffset); + if (ctx.isOverwrite() && LineDocumentUtils.getWordStart(lineDocument, end) == offset) { + String textEnd = doc.getText(end, 1); + while(baseIndent < 0 && textEnd.endsWith(".")) { + end = LineDocumentUtils.getWordEnd(lineDocument, end + 1); + textEnd = doc.getText(end, 1); + } + if (baseIndent < 0 && textEnd.endsWith("=") || textEnd.endsWith(":")) { + end++; + } + doc.remove(offset, Math.max(caretOffset, end) - offset); + } else if (offset < caretOffset) { + doc.remove(offset, caretOffset - offset); + } + StringBuilder sb = new StringBuilder(); + String name = propName.substring(idx); + String[] parts = name.split("\\."); + if (baseIndent < 0) { + int num = 1; + for (int i = 0; i < parts.length; i++) { + String part = parts[i]; + if ("*".equals(part)) { + sb.append("${PAR#" + num++ + " default=\"\"}"); + } else { + sb.append(part); + } + if (i < parts.length - 1) { + sb.append("."); + } else { + sb.append("=${cursor}"); + } + } + } else { + int lineStart = LineDocumentUtils.getLineStart(lineDocument, caretOffset); + int lineIndent = IndentUtils.lineIndent(doc, lineStart); + ArrayUtilities.appendSpaces(sb, baseIndent - lineIndent); + int indent = baseIndent; + int num = 1; + for (int i = 0; i < parts.length; i++) { + String part = parts[i]; + if ("*".equals(part)) { + sb.append("${PAR#" + num++ + " default=\"\"}"); + } else { + sb.append(part); + } + if (i < parts.length - 1) { + sb.append(":\n"); + ArrayUtilities.appendSpaces(sb, (indent = indent + indentLevelSize)); + } else { + sb.append(": ${cursor}"); + } + } + } + CodeTemplateManager.get(doc).createTemporary(sb.toString()).insert(ctx.getComponent()); + } + } catch (BadLocationException ex) { + Exceptions.printStackTrace(ex); + } + }); + if (propType != null) { + builder.rightHtmlText(escape(FQN.matcher(propType).replaceAll("$2"))); + } + return builder.build(); } @Override - public MicronautConfigCompletionItem createTopLevelPropertyItem(String propName, int offset, int baseIndent, int indentLevelSize) { + public CompletionItem createTopLevelPropertyItem(String propName, int offset, int baseIndent, int indentLevelSize) { resultSet.setAnchorOffset(offset); - return MicronautConfigCompletionItem.createTopLevelPropertyItem(propName, offset, baseIndent, indentLevelSize); + return CompletionUtilities.newCompletionItemBuilder(propName) + .iconResource(ICON) + .leftHtmlText(PROPERTY_NAME_COLOR + "<b>" + propName + "</b></font>") + .sortPriority(10) + .onSelect(ctx -> { + try { + Document doc = ctx.getComponent().getDocument(); + LineDocument lineDocument = LineDocumentUtils.as(doc, LineDocument.class); + if (lineDocument != null) { + int caretOffset = ctx.getComponent().getCaretPosition(); + int end = LineDocumentUtils.getWordEnd(lineDocument, caretOffset); + if (ctx.isOverwrite() && LineDocumentUtils.getWordStart(lineDocument, end) == offset) { + String textEnd = doc.getText(end, 1); + if (baseIndent < 0 && textEnd.endsWith(".") || textEnd.endsWith(":")) { + end++; + } + doc.remove(offset, Math.max(caretOffset, end) - offset); + } else if (offset < caretOffset) { + doc.remove(offset, caretOffset - offset); + } + StringBuilder sb = new StringBuilder(); + if (baseIndent < 0) { + sb.append("*".equals(propName) ? "${PAR#1 default=\"\"}" : propName).append(".${cursor completionInvoke}"); + } else { + int lineStart = LineDocumentUtils.getLineStart(lineDocument, caretOffset); + int lineIndent = IndentUtils.lineIndent(doc, lineStart); + ArrayUtilities.appendSpaces(sb, baseIndent - lineIndent); + sb.append("*".equals(propName) ? "${PAR#1 default=\"\"}" : propName).append(":\n"); + ArrayUtilities.appendSpaces(sb, baseIndent + indentLevelSize); + sb.append("${cursor completionInvoke}"); + } + CodeTemplateManager.get(doc).createTemporary(sb.toString()).insert(ctx.getComponent()); + } + } catch (BadLocationException ex) { + Exceptions.printStackTrace(ex); + } + }) + .build(); } })); resultSet.finish(); diff --git a/enterprise/micronaut/src/org/netbeans/modules/micronaut/completion/MicronautDataCompletionCollector.java b/enterprise/micronaut/src/org/netbeans/modules/micronaut/completion/MicronautDataCompletionCollector.java new file mode 100644 index 0000000..3f5abd0 --- /dev/null +++ b/enterprise/micronaut/src/org/netbeans/modules/micronaut/completion/MicronautDataCompletionCollector.java @@ -0,0 +1,53 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.micronaut.completion; + +import java.util.function.Consumer; +import javax.swing.text.Document; +import org.netbeans.api.editor.mimelookup.MimeRegistration; +import org.netbeans.api.lsp.Completion; +import org.netbeans.spi.lsp.CompletionCollector; + +/** + * + * @author Dusan Balek + */ +@MimeRegistration(mimeType = "text/x-java", service = CompletionCollector.class) +public class MicronautDataCompletionCollector implements CompletionCollector { + + @Override + public boolean collectCompletions(Document doc, int offset, Completion.Context context, Consumer<Completion> consumer) { + new MicronautDataCompletionTask().query(doc, offset, new MicronautDataCompletionTask.ItemFactory<Completion>() { + @Override + public Completion createFinderMethodItem(String name, String returnType, int offset) { + Builder builder = CompletionCollector.newBuilder(name).kind(Completion.Kind.Method).sortText(String.format("%04d%s", 10, name)); + if (returnType != null) { + builder.insertText(new StringBuilder("${1:").append(returnType).append("} ").append(name).append("$0()").toString()); + builder.insertTextFormat(Completion.TextFormat.Snippet); + } + return builder.build(); + } + @Override + public Completion createFinderMethodNameItem(String prefix, String name, int offset) { + return CompletionCollector.newBuilder(prefix + name).kind(Completion.Kind.Method).sortText(String.format("%04d%s", 10, name)).build(); + } + }).stream().forEach(consumer); + return true; + } +} diff --git a/enterprise/micronaut/src/org/netbeans/modules/micronaut/completion/MicronautDataCompletionProvider.java b/enterprise/micronaut/src/org/netbeans/modules/micronaut/completion/MicronautDataCompletionProvider.java new file mode 100644 index 0000000..074657f --- /dev/null +++ b/enterprise/micronaut/src/org/netbeans/modules/micronaut/completion/MicronautDataCompletionProvider.java @@ -0,0 +1,103 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.micronaut.completion; + +import javax.swing.text.BadLocationException; +import javax.swing.text.Document; +import javax.swing.text.JTextComponent; +import org.netbeans.api.editor.mimelookup.MimeRegistration; +import org.netbeans.lib.editor.codetemplates.api.CodeTemplateManager; +import org.netbeans.spi.editor.completion.CompletionItem; +import org.netbeans.spi.editor.completion.CompletionProvider; +import org.netbeans.spi.editor.completion.CompletionResultSet; +import org.netbeans.spi.editor.completion.CompletionTask; +import org.netbeans.spi.editor.completion.support.AsyncCompletionQuery; +import org.netbeans.spi.editor.completion.support.AsyncCompletionTask; +import org.netbeans.spi.editor.completion.support.CompletionUtilities; +import org.openide.util.Exceptions; + +/** + * + * @author Dusan Balek + */ +@MimeRegistration(mimeType = "text/x-java", service = CompletionProvider.class, position = 250) +public final class MicronautDataCompletionProvider implements CompletionProvider { + + @Override + public CompletionTask createTask(int queryType, JTextComponent component) { + switch (queryType) { + case COMPLETION_ALL_QUERY_TYPE: + case COMPLETION_QUERY_TYPE: + return new AsyncCompletionTask(new MicronautDataCompletionQuery(), component); + } + return null; + } + + @Override + public int getAutoQueryTypes(JTextComponent component, String typedText) { + return 0; + } + + private static class MicronautDataCompletionQuery extends AsyncCompletionQuery { + + private static final String ICON = "org/netbeans/modules/micronaut/resources/micronaut.png"; + + @Override + protected void query(CompletionResultSet resultSet, Document doc, int caretOffset) { + MicronautDataCompletionTask task = new MicronautDataCompletionTask(); + resultSet.addAllItems(task.query(doc, caretOffset, new MicronautDataCompletionTask.ItemFactory<CompletionItem>() { + @Override + public CompletionItem createFinderMethodItem(String name, String returnType, int offset) { + CompletionUtilities.CompletionItemBuilder builder = CompletionUtilities.newCompletionItemBuilder(name) + .iconResource(ICON) + .leftHtmlText("<b>" + name + "</b>") + .sortPriority(10); + if (returnType != null) { + builder.onSelect(ctx -> { + final Document doc = ctx.getComponent().getDocument(); + try { + doc.remove(offset, ctx.getComponent().getCaretPosition() - offset); + } catch (BadLocationException ex) { + Exceptions.printStackTrace(ex); + } + String template = "${PAR#1 default=\"" + returnType + "\"} " + name + "${cursor completionInvoke}()"; + CodeTemplateManager.get(doc).createTemporary(template).insert(ctx.getComponent()); + }); + } else { + builder.startOffset(offset); + } + return builder.build(); + } + + @Override + public CompletionItem createFinderMethodNameItem(String prefix, String name, int offset) { + return CompletionUtilities.newCompletionItemBuilder(prefix + name) + .startOffset(offset) + .iconResource(ICON) + .leftHtmlText( prefix + "<b>" + name + "</b>") + .sortPriority(10) + .sortText(name) + .build(); + } + })); + resultSet.setAnchorOffset(task.getAnchorOffset()); + resultSet.finish(); + } + } +} diff --git a/enterprise/micronaut/src/org/netbeans/modules/micronaut/completion/MicronautDataCompletionTask.java b/enterprise/micronaut/src/org/netbeans/modules/micronaut/completion/MicronautDataCompletionTask.java new file mode 100644 index 0000000..e025394 --- /dev/null +++ b/enterprise/micronaut/src/org/netbeans/modules/micronaut/completion/MicronautDataCompletionTask.java @@ -0,0 +1,434 @@ +/* + * 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.micronaut.completion; + +import com.sun.source.tree.MethodTree; +import com.sun.source.tree.Tree; +import com.sun.source.tree.VariableTree; +import com.sun.source.util.SourcePositions; +import com.sun.source.util.TreePath; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.prefs.PreferenceChangeEvent; +import java.util.prefs.PreferenceChangeListener; +import java.util.prefs.Preferences; +import java.util.regex.Pattern; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.Modifier; +import javax.lang.model.element.TypeElement; +import javax.lang.model.type.DeclaredType; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; +import javax.lang.model.util.ElementFilter; +import javax.lang.model.util.Types; +import javax.swing.text.Document; +import org.netbeans.api.editor.mimelookup.MimeLookup; +import org.netbeans.api.java.lexer.JavaTokenId; +import org.netbeans.api.java.source.CompilationController; +import org.netbeans.api.java.source.CompilationInfo; +import org.netbeans.api.java.source.JavaSource; +import org.netbeans.api.java.source.TreeUtilities; +import org.netbeans.api.java.source.TypeUtilities; +import org.netbeans.api.lexer.TokenSequence; +import org.netbeans.modules.parsing.api.ParserManager; +import org.netbeans.modules.parsing.api.ResultIterator; +import org.netbeans.modules.parsing.api.Source; +import org.netbeans.modules.parsing.api.UserTask; +import org.netbeans.modules.parsing.spi.ParseException; +import org.openide.util.Exceptions; +import org.openide.util.WeakListeners; + +/** + * + * @author Dusan Balek + */ +public class MicronautDataCompletionTask { + + private static final String REPOSITORY_ANNOTATION_NAME = "io.micronaut.data.annotation.Repository"; + private static final String REPOSITORY_TYPE_NAME = "io.micronaut.data.repository.GenericRepository"; + private static final String GET = "get"; + private static final List<String> QUERY_PATTERNS = new ArrayList<>(Arrays.asList("find", "get", "query", "read", "retrieve", "search")); + private static final List<String> SPECIAL_QUERY_PATTERNS = new ArrayList<>(Arrays.asList("count", "countDistinct", "delete", "exists", "update")); + private static final List<String> QUERY_PROJECTIONS = new ArrayList<>(Arrays.asList("", "Avg", "Distinct", "Max", "Min", "Sum")); + private static final List<String> CRITERION_EXPRESSIONS = new ArrayList<>(Arrays.asList("", "After", "Before", "Contains", "StartingWith", "StartsWith", "EndingWith", "EndsWith", + "Equal", "Equals", "NotEqual", "NotEquals", "GreaterThan", "GreaterThanEquals", "LessThan", "LessThanEquals", "Like", "Ilike", "In", "InList", "InRange", "Between", + "IsNull", "IsNotNull", "IsEmpty", "IsNotEmpty", "True", "False")); + private static final List<String> COMPOSE_EXPRESSIONS = new ArrayList<>(Arrays.asList("And", "Or")); + private static final String BY = "By"; + private static final String ORDER_BY = "OrderBy"; + private static final String COUNT = "count"; + private static final String EXISTS = "exists"; + private static final String EMPTY = ""; + private static final String COMPLETION_CASE_SENSITIVE = "completion-case-sensitive"; // NOI18N + private static final boolean COMPLETION_CASE_SENSITIVE_DEFAULT = true; + private static final String JAVA_COMPLETION_SUBWORDS = "javaCompletionSubwords"; //NOI18N + private static final boolean JAVA_COMPLETION_SUBWORDS_DEFAULT = false; + private static final PreferenceChangeListener preferencesTracker = new PreferenceChangeListener() { + @Override + public void preferenceChange(PreferenceChangeEvent evt) { + String settingName = evt == null ? null : evt.getKey(); + if (settingName == null || COMPLETION_CASE_SENSITIVE.equals(settingName)) { + caseSensitive = preferences.getBoolean(COMPLETION_CASE_SENSITIVE, COMPLETION_CASE_SENSITIVE_DEFAULT); + } + if (settingName == null || JAVA_COMPLETION_SUBWORDS.equals(settingName)) { + javaCompletionSubwords = preferences.getBoolean(JAVA_COMPLETION_SUBWORDS, JAVA_COMPLETION_SUBWORDS_DEFAULT); + } + } + }; + private static final AtomicBoolean inited = new AtomicBoolean(false); + + private static Preferences preferences; + private static boolean caseSensitive = COMPLETION_CASE_SENSITIVE_DEFAULT; + private static boolean javaCompletionSubwords = JAVA_COMPLETION_SUBWORDS_DEFAULT; + private static String cachedPrefix = null; + private static Pattern cachedCamelCasePattern = null; + private static Pattern cachedSubwordsPattern = null; + + private int anchorOffset; + + public static interface ItemFactory<T> { + T createFinderMethodItem(String name, String returnType, int offset); + T createFinderMethodNameItem(String prefix, String name, int offset); + } + + public <T> List<T> query(Document doc, int caretOffset, ItemFactory<T> factory) { + List<T> items = new ArrayList<>(); + try { + ParserManager.parse(Collections.singleton(Source.create(doc)), new UserTask() { + @Override + public void run(ResultIterator resultIterator) throws Exception { + CompilationController cc = CompilationController.get(resultIterator.getParserResult(caretOffset)); + cc.toPhase(JavaSource.Phase.PARSED); + anchorOffset = caretOffset; + String prefix = EMPTY; + TokenSequence<JavaTokenId> ts = cc.getTokenHierarchy().tokenSequence(JavaTokenId.language()); + if (ts.move(anchorOffset) == 0 || !ts.moveNext()) { + ts.movePrevious(); + } + int len = anchorOffset - ts.offset(); + if (len > 0 && ts.token().length() >= len) { + if (ts.token().id() == JavaTokenId.IDENTIFIER || ts.token().id().primaryCategory().startsWith("keyword") || + ts.token().id().primaryCategory().startsWith("string") || ts.token().id().primaryCategory().equals("literal")) { + prefix = ts.token().text().toString().substring(0, len); + anchorOffset = ts.offset(); + } + } + Consumer consumer = (namePrefix, name, type) -> { + items.add(type != null ? factory.createFinderMethodItem(name, type, anchorOffset) : factory.createFinderMethodNameItem(namePrefix, name, anchorOffset)); + }; + TreeUtilities treeUtilities = cc.getTreeUtilities(); + SourcePositions sp = cc.getTrees().getSourcePositions(); + TreePath path = treeUtilities.pathFor(anchorOffset); + switch (path.getLeaf().getKind()) { + case CLASS: + case INTERFACE: + resolve(cc, path, prefix, true, consumer); + break; + case METHOD: + Tree returnType = ((MethodTree) path.getLeaf()).getReturnType(); + if (returnType != null && findLastNonWhitespaceToken(ts, (int) sp.getEndPosition(path.getCompilationUnit(), returnType), anchorOffset) == null) { + resolve(cc, path.getParentPath(), prefix, false, consumer); + } + break; + case VARIABLE: + Tree type = ((VariableTree) path.getLeaf()).getType(); + if (type != null && findLastNonWhitespaceToken(ts, (int) sp.getEndPosition(path.getCompilationUnit(), type), anchorOffset) == null) { + TreePath parentPath = path.getParentPath(); + if (parentPath.getLeaf().getKind() == Tree.Kind.CLASS || parentPath.getLeaf().getKind() == Tree.Kind.INTERFACE) { + resolve(cc, parentPath, prefix, false, consumer); + } + } + break; + + } + } + }); + } catch (ParseException ex) { + Exceptions.printStackTrace(ex); + } + return items; + } + + public int getAnchorOffset() { + return anchorOffset; + } + + private <T> void resolve(CompilationController cc, TreePath path, String prefix, boolean full, Consumer consumer) throws IOException { + cc.toPhase(JavaSource.Phase.ELEMENTS_RESOLVED); + TypeUtilities tu = cc.getTypeUtilities(); + TypeElement entity = getEntityFor(cc, path); + if (entity != null) { + Map<String, String> prop2Types = new HashMap<>(); + for (ExecutableElement method : ElementFilter.methodsIn(entity.getEnclosedElements())) { + String methodName = method.getSimpleName().toString(); + if (methodName.startsWith(GET) && method.getParameters().isEmpty()) { + prop2Types.put(methodName.substring(GET.length()), tu.getTypeName(method.getReturnType()).toString()); + } + } + addFindByCompletions(entity, prop2Types, prefix, full, consumer); + } + } + + private void addFindByCompletions(TypeElement entity, Map<String, String> prop2Types, String prefix, boolean full, Consumer consumer) { + for (String pattern : QUERY_PATTERNS) { + String name = pattern + BY; + if (prefix.length() >= name.length() && prefix.startsWith(name)) { + addPropertyCriterionCompletions(prop2Types, name, prefix, consumer); + } else if (startsWith(name, prefix)) { + consumer.accept(EMPTY, name, full ? entity.getSimpleName().toString() : null); + } + for (String projection : QUERY_PROJECTIONS) { + for (String propName : prop2Types.keySet()) { + name = pattern + projection + propName + BY; + if (prefix.length() >= name.length() && prefix.startsWith(name)) { + addPropertyCriterionCompletions(prop2Types, name, prefix, consumer); + } else if (startsWith(name, prefix)) { + consumer.accept(EMPTY, name, full ? prop2Types.get(propName) : null); + } + } + } + } + for (String pattern : SPECIAL_QUERY_PATTERNS) { + for (String propName : prop2Types.keySet()) { + String name = pattern + propName + BY; + if (prefix.length() >= name.length() && prefix.startsWith(name)) { + addPropertyCriterionCompletions(prop2Types, name, prefix, consumer); + } else if (startsWith(name, prefix)) { + consumer.accept(EMPTY, name, full ? name.startsWith(COUNT) ? "int" : name.startsWith(EXISTS) ? "boolean" : "void" : null); + } + } + } + } + + private void addPropertyCriterionCompletions(Map<String, String> prop2Types, String namePrefix, String prefix, Consumer consumer) { + for (String propName : prop2Types.keySet()) { + for (String criterion : CRITERION_EXPRESSIONS) { + String name = propName + criterion; + if (prefix.length() >= namePrefix.length() + name.length() && prefix.startsWith(namePrefix + name)) { + addComposeAndOrderCompletions(prop2Types, namePrefix + name, prefix, consumer); + } else if (startsWith(namePrefix + name, prefix)) { + consumer.accept(namePrefix, name, null); + } + } + } + } + + private void addComposeAndOrderCompletions(Map<String, String> prop2Types, String namePrefix, String prefix, Consumer consumer) { + for (String name : COMPOSE_EXPRESSIONS) { + if (prefix.length() >= namePrefix.length() + name.length() && prefix.startsWith(namePrefix + name)) { + addPropertyCriterionCompletions(prop2Types, namePrefix + name, prefix, consumer); + } else if (startsWith(namePrefix + name, prefix)) { + consumer.accept(namePrefix, name, null); + } + } + for (String propName : prop2Types.keySet()) { + String name = ORDER_BY + propName; + if (prefix.length() < namePrefix.length() + name.length() && startsWith(namePrefix + name, prefix)) { + consumer.accept(namePrefix, name, null); + } + } + } + + private static TypeElement getEntityFor(CompilationInfo info, TreePath path) { + TypeElement te = (TypeElement) info.getTrees().getElement(path); + if (te.getModifiers().contains(Modifier.ABSTRACT)) { + if (checkForRepositoryAnnotation(te.getAnnotationMirrors())) { + Types types = info.getTypes(); + TypeMirror repositoryType = types.erasure(info.getElements().getTypeElement(REPOSITORY_TYPE_NAME).asType()); + for (TypeMirror iface : te.getInterfaces()) { + if (iface.getKind() == TypeKind.DECLARED && types.isSubtype(types.erasure(iface), repositoryType)) { + List<? extends TypeMirror> typeArguments = ((DeclaredType) iface).getTypeArguments(); + if (!typeArguments.isEmpty()) { + TypeMirror entityType = typeArguments.get(0); + if (entityType != null && entityType.getKind() == TypeKind.DECLARED) { + return (TypeElement) ((DeclaredType) entityType).asElement(); + } + } + } + } + } + } + return null; + } + + private static boolean checkForRepositoryAnnotation(List<? extends AnnotationMirror> annotations) { + for (AnnotationMirror annotation : annotations) { + DeclaredType annotationType = annotation.getAnnotationType(); + if (REPOSITORY_ANNOTATION_NAME.contentEquals(((TypeElement) annotationType.asElement()).getQualifiedName()) || checkForRepositoryAnnotation(annotationType.getAnnotationMirrors())) { + return true; + } + } + return false; + } + + private static TokenSequence<JavaTokenId> findLastNonWhitespaceToken(TokenSequence<JavaTokenId> ts, int startPos, int endPos) { + ts.move(endPos); + TokenSequence<JavaTokenId> last = previousNonWhitespaceToken(ts); + if (last == null || last.offset() < startPos) { + return null; + } + return last; + } + + private static TokenSequence<JavaTokenId> previousNonWhitespaceToken(TokenSequence<JavaTokenId> ts) { + while (ts.movePrevious()) { + switch (ts.token().id()) { + case WHITESPACE: + case LINE_COMMENT: + case BLOCK_COMMENT: + case JAVADOC_COMMENT: + break; + default: + return ts; + } + } + return null; + } + + private static boolean startsWith(String theString, String prefix) { + return isCamelCasePrefix(prefix) ? isCaseSensitive() + ? startsWithCamelCase(theString, prefix) + : startsWithCamelCase(theString, prefix) || startsWithPlain(theString, prefix) + : startsWithPlain(theString, prefix); + } + + private static boolean isCamelCasePrefix(String prefix) { + if (prefix == null || prefix.length() < 2 || prefix.charAt(0) == '"') { + return false; + } + for (int i = 1; i < prefix.length(); i++) { + if (Character.isUpperCase(prefix.charAt(i))) { + return true; + } + } + return false; + } + + private static boolean isCaseSensitive() { + lazyInit(); + return caseSensitive; + } + + public static boolean isSubwordSensitive() { + lazyInit(); + return javaCompletionSubwords; + } + + private static boolean startsWithPlain(String theString, String prefix) { + if (theString == null || theString.length() == 0) { + return false; + } + if (prefix == null || prefix.length() == 0) { + return true; + } + if (isSubwordSensitive()) { + if (!prefix.equals(cachedPrefix)) { + cachedCamelCasePattern = null; + cachedSubwordsPattern = null; + } + if (cachedSubwordsPattern == null) { + cachedPrefix = prefix; + String patternString = createSubwordsPattern(prefix); + cachedSubwordsPattern = patternString != null ? Pattern.compile(patternString) : null; + } + if (cachedSubwordsPattern != null && cachedSubwordsPattern.matcher(theString).matches()) { + return true; + } + } + return isCaseSensitive() ? theString.startsWith(prefix) : theString.toLowerCase(Locale.ENGLISH).startsWith(prefix.toLowerCase(Locale.ENGLISH)); + } + + private static String createSubwordsPattern(String prefix) { + StringBuilder sb = new StringBuilder(3 + 8 * prefix.length()); + sb.append(".*?"); + for (int i = 0; i < prefix.length(); i++) { + char charAt = prefix.charAt(i); + if (!Character.isJavaIdentifierPart(charAt)) { + return null; + } + if (Character.isLowerCase(charAt)) { + sb.append("["); + sb.append(charAt); + sb.append(Character.toUpperCase(charAt)); + sb.append("]"); + } else { + //keep uppercase characters as beacons + // for example: java.lang.System.sIn -> setIn + sb.append(charAt); + } + sb.append(".*?"); + } + return sb.toString(); + } + + private static boolean startsWithCamelCase(String theString, String prefix) { + if (theString == null || theString.length() == 0 || prefix == null || prefix.length() == 0) { + return false; + } + if (!prefix.equals(cachedPrefix)) { + cachedCamelCasePattern = null; + cachedSubwordsPattern = null; + } + if (cachedCamelCasePattern == null) { + StringBuilder sb = new StringBuilder(); + int lastIndex = 0; + int index; + do { + index = findNextUpper(prefix, lastIndex + 1); + String token = prefix.substring(lastIndex, index == -1 ? prefix.length() : index); + sb.append(token); + sb.append(index != -1 ? "[\\p{javaLowerCase}\\p{Digit}_\\$]*" : ".*"); + lastIndex = index; + } while (index != -1); + cachedPrefix = prefix; + cachedCamelCasePattern = Pattern.compile(sb.toString()); + } + return cachedCamelCasePattern.matcher(theString).matches(); + } + + private static int findNextUpper(String text, int offset) { + for (int i = offset; i < text.length(); i++) { + if (Character.isUpperCase(text.charAt(i))) { + return i; + } + } + return -1; + } + + private static void lazyInit() { + if (inited.compareAndSet(false, true)) { + preferences = MimeLookup.getLookup(JavaTokenId.language().mimeType()).lookup(Preferences.class); + preferences.addPreferenceChangeListener(WeakListeners.create(PreferenceChangeListener.class, preferencesTracker, preferences)); + preferencesTracker.preferenceChange(null); + } + } + + @FunctionalInterface + private static interface Consumer { + void accept(String namePrefix, String name, String type); + } +} diff --git a/enterprise/micronaut/src/org/netbeans/modules/micronaut/newproject/MicronautProjectWizardIterator.java b/enterprise/micronaut/src/org/netbeans/modules/micronaut/newproject/MicronautProjectWizardIterator.java index 1ac79ce..f58b00c 100644 --- a/enterprise/micronaut/src/org/netbeans/modules/micronaut/newproject/MicronautProjectWizardIterator.java +++ b/enterprise/micronaut/src/org/netbeans/modules/micronaut/newproject/MicronautProjectWizardIterator.java @@ -92,6 +92,7 @@ public class MicronautProjectWizardIterator implements WizardDescriptor.Progress public Set instantiate(ProgressHandle handle) throws IOException { try { handle.start(4); + String projectName = (String) wiz.getProperty(PROJECT_NAME); File projFile = FileUtil.normalizeFile((File) wiz.getProperty(PROJECT_LOCATION)); projFile.mkdirs(); handle.progress(1); @@ -105,7 +106,7 @@ public class MicronautProjectWizardIterator implements WizardDescriptor.Progress (Set<MicronautLaunchService.Feature>) wiz.getProperty(FEATURES)); handle.progress(2); FileObject projDir = FileUtil.toFileObject(projFile); - unzip(stream, projDir); + unzip(stream, projDir, projectName + '/'); handle.progress(3); ProjectManager.getDefault().clearNonProjectCache(); Project prj = ProjectManager.getDefault().findProject(projDir); @@ -207,11 +208,14 @@ public class MicronautProjectWizardIterator implements WizardDescriptor.Progress public void removeChangeListener(ChangeListener l) { } - private static void unzip(InputStream stream, FileObject folder) throws IOException { + private static void unzip(InputStream stream, FileObject folder, String prefix) throws IOException { try (ZipInputStream zis = new ZipInputStream(stream)) { ZipEntry zipEntry; while ((zipEntry = zis.getNextEntry()) != null) { String entryName = zipEntry.getName(); + if (entryName.startsWith(prefix)) { + entryName = entryName.substring(prefix.length()); + } if (zipEntry.isDirectory()) { FileUtil.createFolder(folder, entryName); } else { diff --git a/enterprise/micronaut/src/org/netbeans/modules/micronaut/refactor/MicronautRefactoringFactory.java b/enterprise/micronaut/src/org/netbeans/modules/micronaut/refactor/MicronautRefactoringFactory.java index d90a62e..7760e26 100644 --- a/enterprise/micronaut/src/org/netbeans/modules/micronaut/refactor/MicronautRefactoringFactory.java +++ b/enterprise/micronaut/src/org/netbeans/modules/micronaut/refactor/MicronautRefactoringFactory.java @@ -38,7 +38,7 @@ import org.netbeans.api.project.ProjectUtils; import org.netbeans.api.project.SourceGroup; import org.netbeans.modules.micronaut.MicronautConfigProperties; import org.netbeans.modules.micronaut.MicronautConfigUtilities; -import org.netbeans.modules.micronaut.completion.MicronautConfigCompletionItem; +import org.netbeans.modules.micronaut.completion.MicronautConfigCompletionProvider; import org.netbeans.modules.refactoring.api.AbstractRefactoring; import org.netbeans.modules.refactoring.api.Problem; import org.netbeans.modules.refactoring.api.Scope; @@ -210,7 +210,7 @@ public class MicronautRefactoringFactory implements RefactoringPluginFactory { if (idx < 0) { sb.append(text); } else { - sb.append(MicronautConfigCompletionItem.PROPERTY_NAME_COLOR).append("<b>"); + sb.append(MicronautConfigCompletionProvider.PROPERTY_NAME_COLOR).append("<b>"); sb.append(text.substring(0, idx)); sb.append("</b></font>"); sb.append(text.substring(idx)); diff --git a/ide/editor.completion/apichanges.xml b/ide/editor.completion/apichanges.xml index 25cd2a2..92daa52 100644 --- a/ide/editor.completion/apichanges.xml +++ b/ide/editor.completion/apichanges.xml @@ -84,6 +84,21 @@ is the proper place. <!-- ACTUAL CHANGES BEGIN HERE: --> <changes> + <change id="CompletionUtilities.newCompletionItemBuilder"> + <api name="completion"/> + <summary>Addition of CompletionUtilities.newCompletionItemBuilder() method</summary> + <version major="1" minor="60"/> + <date day="26" month="11" year="2021"/> + <author login="dbalek"/> + <compatibility addition="yes"/> + <description> + <p> + <code>CompletionUtilities.newCompletionItemBuilder()</code> method was added + to allow for creation of simple <code>CompletionItem</code>s. + </p> + </description> + </change> + <change id="CompositeCompletionItem"> <api name="completion"/> <summary>Addition of CompositeCompletionItem</summary> diff --git a/ide/editor.completion/nbproject/project.properties b/ide/editor.completion/nbproject/project.properties index 20de06b..8f0b16f 100644 --- a/ide/editor.completion/nbproject/project.properties +++ b/ide/editor.completion/nbproject/project.properties @@ -19,4 +19,4 @@ javac.compilerargs=-Xlint:unchecked javac.source=1.8 javadoc.arch=${basedir}/arch.xml javadoc.apichanges=${basedir}/apichanges.xml -spec.version.base=1.59.0 +spec.version.base=1.60.0 diff --git a/ide/editor.completion/src/org/netbeans/modules/editor/completion/CompletionSupportSpiPackageAccessor.java b/ide/editor.completion/src/org/netbeans/modules/editor/completion/CompletionSupportSpiPackageAccessor.java new file mode 100644 index 0000000..c09d0c9 --- /dev/null +++ b/ide/editor.completion/src/org/netbeans/modules/editor/completion/CompletionSupportSpiPackageAccessor.java @@ -0,0 +1,41 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.netbeans.modules.editor.completion; + +import javax.swing.text.JTextComponent; +import org.netbeans.spi.editor.completion.support.CompletionUtilities; + +public abstract class CompletionSupportSpiPackageAccessor { + + private static CompletionSupportSpiPackageAccessor INSTANCE; + + public static CompletionSupportSpiPackageAccessor get() { + return INSTANCE; + } + + public static void register(CompletionSupportSpiPackageAccessor accessor) { + if (INSTANCE != null) { + throw new IllegalStateException("Already registered"); + } + INSTANCE = accessor; + } + + public abstract CompletionUtilities.OnSelectContext createOnSelectContext(JTextComponent component, boolean overwrite); +} diff --git a/ide/editor.completion/src/org/netbeans/modules/editor/completion/SimpleCompletionItem.java b/ide/editor.completion/src/org/netbeans/modules/editor/completion/SimpleCompletionItem.java new file mode 100644 index 0000000..f34c651 --- /dev/null +++ b/ide/editor.completion/src/org/netbeans/modules/editor/completion/SimpleCompletionItem.java @@ -0,0 +1,172 @@ +/* + * 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.editor.completion; + +import java.awt.Color; +import java.awt.Font; +import java.awt.Graphics; +import java.awt.event.InputEvent; +import java.awt.event.KeyEvent; +import java.util.function.Consumer; +import java.util.function.Supplier; +import javax.swing.ImageIcon; +import javax.swing.text.BadLocationException; +import javax.swing.text.JTextComponent; +import org.netbeans.api.editor.completion.Completion; +import org.netbeans.editor.BaseDocument; +import org.netbeans.spi.editor.completion.CompletionItem; +import org.netbeans.spi.editor.completion.CompletionTask; +import org.netbeans.spi.editor.completion.support.CompletionUtilities; +import org.openide.util.ImageUtilities; + +/** + * + * @author Dusan Balek + */ +public class SimpleCompletionItem implements CompletionItem { + + private final String insertText; + private final int startOffset; + private final int endOffset; + private final String iconResource; + private final String leftHtmlText; + private final String rightHtmlText; + private final int sortPriority; + private final CharSequence sortText; + private final Supplier<CompletionTask> documentationTask; + private final Supplier<CompletionTask> tooltipTask; + private final Consumer<CompletionUtilities.OnSelectContext> onSelectCallback; + + private ImageIcon icon; + + public SimpleCompletionItem(String insertText, int startOffset, int endOffset, String iconResource, String leftHtmlText, String rightHtmlText, + int sortPriority, CharSequence sortText, Supplier<CompletionTask> documentationTask, Supplier<CompletionTask> tooltipTask, + Consumer<CompletionUtilities.OnSelectContext> onSelectCallback) { + this.insertText = insertText; + this.startOffset = startOffset; + this.endOffset = endOffset; + this.iconResource = iconResource; + this.leftHtmlText = leftHtmlText; + this.rightHtmlText = rightHtmlText; + this.sortPriority = sortPriority; + this.sortText = sortText; + this.documentationTask = documentationTask; + this.tooltipTask = tooltipTask; + this.onSelectCallback = onSelectCallback; + } + + @Override + public void defaultAction(JTextComponent component) { + if (component != null) { + Completion.get().hideDocumentation(); + Completion.get().hideCompletion(); + process(component, false); + } + } + + @Override + public void processKeyEvent(KeyEvent evt) { + if (evt.getID() == KeyEvent.KEY_PRESSED && evt.getKeyCode() == KeyEvent.VK_ENTER && (evt.getModifiers() & InputEvent.CTRL_MASK) > 0) { + JTextComponent component = (JTextComponent)evt.getSource(); + Completion.get().hideDocumentation(); + Completion.get().hideCompletion(); + process(component, true); + evt.consume(); + } + } + + @Override + public int getPreferredWidth(Graphics g, Font defaultFont) { + return CompletionUtilities.getPreferredWidth(leftHtmlText != null ? leftHtmlText : insertText, rightHtmlText, g, defaultFont); + } + + @Override + public void render(Graphics g, Font defaultFont, Color defaultColor, Color backgroundColor, int width, int height, boolean selected) { + CompletionUtilities.renderHtml(getIcon(), leftHtmlText != null ? leftHtmlText : insertText, rightHtmlText, g, defaultFont, defaultColor, width, height, selected); + } + + @Override + public CompletionTask createDocumentationTask() { + if (documentationTask != null) { + return documentationTask.get(); + } + return null; + } + + @Override + public CompletionTask createToolTipTask() { + if (tooltipTask != null) { + tooltipTask.get(); + } + return null; + } + + @Override + public boolean instantSubstitution(JTextComponent component) { + return false; + } + + @Override + public int getSortPriority() { + return sortPriority; + } + + @Override + public CharSequence getSortText() { + return sortText != null ? sortText : insertText; + } + + @Override + public CharSequence getInsertPrefix() { + return insertText; + } + + private void process(JTextComponent component, boolean overwrite) { + if (onSelectCallback != null) { + CompletionUtilities.OnSelectContext ctx = CompletionSupportSpiPackageAccessor.get().createOnSelectContext(component, overwrite); + onSelectCallback.accept(ctx); + } else { + final BaseDocument doc = (BaseDocument) component.getDocument(); + doc.runAtomic (new Runnable() { + @Override + public void run() { + try { + if (startOffset < 0) { + if (overwrite && endOffset > component.getCaretPosition()) { + doc.remove(component.getCaretPosition(), endOffset - component.getCaretPosition()); + } + doc.insertString(component.getCaretPosition(), insertText, null); + } else { + doc.remove(startOffset, (overwrite && endOffset > component.getCaretPosition() ? endOffset : component.getCaretPosition()) - startOffset); + doc.insertString(startOffset, insertText, null); + } + } catch (BadLocationException e) { + } + } + }); + } + } + + private ImageIcon getIcon() { + if (icon == null && iconResource != null) { + icon = ImageUtilities.loadImageIcon(iconResource, false); + } + return icon; + } +} diff --git a/ide/editor.completion/src/org/netbeans/spi/editor/completion/support/CompletionUtilities.java b/ide/editor.completion/src/org/netbeans/spi/editor/completion/support/CompletionUtilities.java index fda21be..3d5cc35 100644 --- a/ide/editor.completion/src/org/netbeans/spi/editor/completion/support/CompletionUtilities.java +++ b/ide/editor.completion/src/org/netbeans/spi/editor/completion/support/CompletionUtilities.java @@ -23,8 +23,15 @@ import java.awt.Color; import java.awt.Font; import java.awt.FontMetrics; import java.awt.Graphics; +import java.util.function.Consumer; +import java.util.function.Supplier; import javax.swing.ImageIcon; +import javax.swing.text.JTextComponent; +import org.netbeans.modules.editor.completion.CompletionSupportSpiPackageAccessor; import org.netbeans.modules.editor.completion.PatchedHtmlRenderer; +import org.netbeans.modules.editor.completion.SimpleCompletionItem; +import org.netbeans.spi.editor.completion.CompletionItem; +import org.netbeans.spi.editor.completion.CompletionTask; /** * Various code completion utilities including completion item @@ -36,6 +43,10 @@ import org.netbeans.modules.editor.completion.PatchedHtmlRenderer; public final class CompletionUtilities { + static { + CompletionSupportSpiPackageAccessor.register(new SpiAccessor()); + } + /** * The gap between left edge and icon. */ @@ -160,5 +171,220 @@ public final class CompletionUtilities { defaultFont, defaultColor, PatchedHtmlRenderer.STYLE_TRUNCATE, true, selected); } } - + + /** + * Creates a builder for simple {@link CompletionItem} instances. + * + * @param insertText a text to be inserted into a document when selecting the item. + * @return newly created builder + * + * @since 1.60 + */ + public static CompletionItemBuilder newCompletionItemBuilder(String insertText) { + return new CompletionItemBuilder(insertText); + } + + /** + * Builder for simple {@link CompletionItem} instances. + * <br/> + * Example usage: + * <pre> + * CompletionUtilities.newCompletionItemBuilder(insertText) + * .startOffset(offset) + * .iconResource(iconPath) + * .leftHtmlText("<b>" + label + "</b>") + * .sortPriority(10) + * .sortText(label) + * .build(); + * </pre> + * + * @since 1.60 + */ + public static final class CompletionItemBuilder { + + private String insertText; + private int startOffset = -1; + private int endOffset = -1; + private String iconResource; + private String leftHtmlText; + private String rightHtmlText; + private int sortPriority = 10000; + private CharSequence sortText; + private Supplier<CompletionTask> documentationTask; + private Supplier<CompletionTask> tooltipTask; + private Consumer<OnSelectContext> onSelectCallback; + + private CompletionItemBuilder(String insertText) { + this.insertText = insertText; + } + + /** + * A text to be inserted into a document when selecting the item. + * + * @since 1.60 + */ + public CompletionItemBuilder insertText(String insertText) { + this.insertText = insertText; + return this; + } + + /** + * Start offset of the region to be removed on the item's selection. If omitted, + * the caret offset would be used. + * + * @since 1.60 + */ + public CompletionItemBuilder startOffset(int offset) { + this.startOffset = offset; + return this; + } + + /** + * Start offset of the region to be removed on the item's selection. If omitted, + * the caret offset would be used. + * + * @since 1.60 + */ + public CompletionItemBuilder endOffset(int offset) { + this.endOffset = offset; + return this; + } + + /** + * Resource path of the icon. It may be null which means that no icon will be displayed. + * + * @since 1.60 + */ + public CompletionItemBuilder iconResource(String iconResource) { + this.iconResource = iconResource; + return this; + } + + /** + * An html text that will be displayed on the left side of the item next to the icon. + * If omitted, insertText would be used instead. + * + * @since 1.60 + */ + public CompletionItemBuilder leftHtmlText(String leftHtmlText) { + this.leftHtmlText = leftHtmlText; + return this; + } + + /** + * An html text that will be aligned to the right edge of the item. + * + * @since 1.60 + */ + public CompletionItemBuilder rightHtmlText(String rightHtmlText) { + this.rightHtmlText = rightHtmlText; + return this; + } + + /** + * Item's priority. A lower value means a lower index of the item in the completion result list. + * + * @since 1.60 + */ + public CompletionItemBuilder sortPriority(int sortPriority) { + this.sortPriority = sortPriority; + return this; + } + + /** + * A text used to sort items alphabetically. If omitted, insertText would be used instead. + * + * @since 1.60 + */ + public CompletionItemBuilder sortText(CharSequence sortText) { + this.sortText = sortText; + return this; + } + + /** + * A task used to obtain a documentation associated with the item if there + * is any. + * + * @since 1.60 + */ + public CompletionItemBuilder documentationTask(Supplier<CompletionTask> task) { + this.documentationTask = task; + return this; + } + + /** + * A task used to obtain a tooltip hint associated with the item if there + * is any. + * + * @since 1.60 + */ + public CompletionItemBuilder tooltipTask(Supplier<CompletionTask> task) { + this.tooltipTask = task; + return this; + } + + /** + * A callback to process the item insertion. Should be used for complex cases + * when a simple insertText insertion is not sufficient. + * + * @since 1.60 + */ + public CompletionItemBuilder onSelect(Consumer<OnSelectContext> callback) { + this.onSelectCallback = callback; + return this; + } + + /** + * Builds completion item. + * + * @since 1.60 + */ + public CompletionItem build() { + return new SimpleCompletionItem(insertText, startOffset, endOffset, iconResource, leftHtmlText, rightHtmlText, + sortPriority, sortText, documentationTask, tooltipTask, onSelectCallback); + } + + } + + /** + * A parameter passed to CompletionItemBuilder's onSelect callback. + * + * @since 1.60 + */ + public static final class OnSelectContext { + + private final JTextComponent component; + private final boolean overwrite; + + private OnSelectContext(JTextComponent component, boolean overwrite) { + this.component = component; + this.overwrite = overwrite; + } + + /** + * A text component to which the completion item should be inserted. + * + * @since 1.60 + */ + public JTextComponent getComponent() { + return component; + } + + /** + * If true, the inserted completion item should overwrite existing text. + * + * @since 1.60 + */ + public boolean isOverwrite() { + return overwrite; + } + } + + private static final class SpiAccessor extends CompletionSupportSpiPackageAccessor { + + @Override + public OnSelectContext createOnSelectContext(JTextComponent component, boolean overwrite) { + return new OnSelectContext(component, overwrite); + } + } } diff --git a/java/java.lsp.server/vscode/src/extension.ts b/java/java.lsp.server/vscode/src/extension.ts index e508215..57062e8 100644 --- a/java/java.lsp.server/vscode/src/extension.ts +++ b/java/java.lsp.server/vscode/src/extension.ts @@ -514,7 +514,7 @@ function doActivateWithJDK(specifiedJDK: string | null, context: ExtensionContex stdOut = null; } } - let p = launcher.launch(info, "--modules", "--list"); + let p = launcher.launch(info, "--modules", "--list", "-J-XX:PerfMaxStringConstLength=10240"); handleLog(log, "LSP server launching: " + p.pid); p.stdout.on('data', function(d: any) { logAndWaitForEnabled(d.toString(), true); --------------------------------------------------------------------- To unsubscribe, e-mail: commits-unsubscr...@netbeans.apache.org For additional commands, e-mail: commits-h...@netbeans.apache.org For further information about the NetBeans mailing lists, visit: https://cwiki.apache.org/confluence/display/NETBEANS/Mailing+lists