You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@netbeans.apache.org by db...@apache.org on 2021/11/29 15:48:58 UTC

[netbeans] branch master updated: Micronaut data finder methods code completion (#3333)

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 <du...@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-unsubscribe@netbeans.apache.org
For additional commands, e-mail: commits-help@netbeans.apache.org

For further information about the NetBeans mailing lists, visit:
https://cwiki.apache.org/confluence/display/NETBEANS/Mailing+lists