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/09/13 07:37:22 UTC

[netbeans] branch master updated: LSP: Surround With refactorings implemented. (#3157)

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 58dade6  LSP: Surround With refactorings implemented. (#3157)
58dade6 is described below

commit 58dade642b6c3a123172d1dd966fc13b32b83d35
Author: Dusan Balek <du...@oracle.com>
AuthorDate: Mon Sep 13 09:37:03 2021 +0200

    LSP: Surround With refactorings implemented. (#3157)
    
    * LSP: Surround With refactorings implemented.
    * LSP: Code templates added to code completion.
---
 .../codetemplates/GsfCodeTemplateFilter.java       |  14 +-
 ide/editor.codetemplates/apichanges.xml            |  12 +
 .../nbproject/project.properties                   |   2 +-
 ide/editor.codetemplates/nbproject/project.xml     |  25 +-
 .../lib/editor/codetemplates/AbbrevDetection.java  |   2 +-
 .../CodeTemplateCompletionProvider.java            | 149 +++++++++-
 .../CodeTemplateManagerOperation.java              |   6 +-
 .../lib/editor/codetemplates/SurroundWithFix.java  |   2 +-
 .../codetemplates/spi/CodeTemplateFilter.java      |  18 ++
 .../editor/java/JavaCodeTemplateFilter.java        |  16 +-
 .../netbeans/modules/editor/java/Utilities.java    |   5 +-
 .../java/editor/resources/DefaultAbbrevs.xml       |  94 +++---
 java/java.lsp.server/nbproject/project.xml         |   9 +
 .../ExtractSuperclassOrInterfaceRefactoring.java   |  10 +-
 .../java/lsp/server/protocol/MoveRefactoring.java  |   8 +-
 .../lsp/server/protocol/PullUpRefactoring.java     |  10 +-
 .../lsp/server/protocol/PushDownRefactoring.java   |  15 +-
 .../java/lsp/server/protocol/SurroundWithHint.java | 329 +++++++++++++++++++++
 .../java/lsp/server/protocol/ServerTest.java       |  79 +++++
 19 files changed, 709 insertions(+), 96 deletions(-)

diff --git a/ide/csl.api/src/org/netbeans/modules/csl/editor/codetemplates/GsfCodeTemplateFilter.java b/ide/csl.api/src/org/netbeans/modules/csl/editor/codetemplates/GsfCodeTemplateFilter.java
index 8ddd4b2..ef98297 100644
--- a/ide/csl.api/src/org/netbeans/modules/csl/editor/codetemplates/GsfCodeTemplateFilter.java
+++ b/ide/csl.api/src/org/netbeans/modules/csl/editor/codetemplates/GsfCodeTemplateFilter.java
@@ -40,10 +40,9 @@ public class GsfCodeTemplateFilter implements CodeTemplateFilter {
     private int endOffset;
     private Set<String> templates;
     
-    private GsfCodeTemplateFilter(JTextComponent component, int offset) {
-        this.startOffset = offset;
-        this.endOffset = component.getSelectionStart() == offset ? component.getSelectionEnd() : -1;
-        Document doc = component.getDocument();
+    private GsfCodeTemplateFilter(Document doc, int startOffset, int endOffset) {
+        this.startOffset = startOffset;
+        this.endOffset = endOffset;
         CodeCompletionHandler completer = doc == null ? null : GsfCompletionProvider.getCompletable(doc, startOffset);
             
         if (completer != null) {
@@ -68,7 +67,12 @@ public class GsfCodeTemplateFilter implements CodeTemplateFilter {
         
         @Override
         public CodeTemplateFilter createFilter(JTextComponent component, int offset) {
-            return new GsfCodeTemplateFilter(component, offset);
+            return createFilter(component.getDocument(), offset, component.getSelectionStart() == offset ? component.getSelectionEnd() : -1);
+        }
+
+        @Override
+        public CodeTemplateFilter createFilter(Document doc, int startOffset, int endOffset) {
+            return new GsfCodeTemplateFilter(doc, startOffset, endOffset);
         }
     }
 
diff --git a/ide/editor.codetemplates/apichanges.xml b/ide/editor.codetemplates/apichanges.xml
index 54177d6..63b6291 100644
--- a/ide/editor.codetemplates/apichanges.xml
+++ b/ide/editor.codetemplates/apichanges.xml
@@ -84,6 +84,18 @@ is the proper place.
     <!-- ACTUAL CHANGES BEGIN HERE: -->
 
     <changes>
+        <change id="CodeTemplateFilter.Factory.createForDoc">
+             <api name="codetemplates"/>
+             <summary>Default method <code>createFilter(Document, int, int)</code> added to <code>CodeTemplateFilter.Factory</code>.</summary>
+             <version major="1" minor="57"/>
+             <date day="9" month="9" year="2021"/>
+             <author login="dbalek"/>
+             <compatibility addition="yes" binary="compatible" deletion="no" deprecation="no" modification="no" semantic="compatible" source="compatible"/>
+             <description>
+                 Added method allows creating code template filters for the given document and range.
+             </description>
+             <class package="org.netbeans.lib.editor.codetemplates.spi" name="CodeTemplateFilter"/>
+        </change>
         <change id="orderingAttribute.added">
              <api name="codetemplates"/>
              <summary>Added <code>ordering</code> for code template parameters.</summary>
diff --git a/ide/editor.codetemplates/nbproject/project.properties b/ide/editor.codetemplates/nbproject/project.properties
index 561dc23..213ea65 100644
--- a/ide/editor.codetemplates/nbproject/project.properties
+++ b/ide/editor.codetemplates/nbproject/project.properties
@@ -20,6 +20,6 @@ javac.source=1.8
 #javadoc.name=EditorCodeTemplates
 javadoc.apichanges=${basedir}/apichanges.xml
 javadoc.arch=${basedir}/arch.xml
-spec.version.base=1.56.0
+spec.version.base=1.57.0
 
 test.config.stableBTD.includes=**/*Test.class
diff --git a/ide/editor.codetemplates/nbproject/project.xml b/ide/editor.codetemplates/nbproject/project.xml
index 1714265..1e05a0c 100644
--- a/ide/editor.codetemplates/nbproject/project.xml
+++ b/ide/editor.codetemplates/nbproject/project.xml
@@ -26,6 +26,15 @@
             <code-name-base>org.netbeans.modules.editor.codetemplates</code-name-base>
             <module-dependencies>
                 <dependency>
+                    <code-name-base>org.netbeans.api.lsp</code-name-base>
+                    <build-prerequisite/>
+                    <compile-dependency/>
+                    <run-dependency>
+                        <release-version>1</release-version>
+                        <specification-version>1.5</specification-version>
+                    </run-dependency>
+                </dependency>
+                <dependency>
                     <code-name-base>org.netbeans.modules.editor</code-name-base>
                     <build-prerequisite/>
                     <compile-dependency/>
@@ -96,19 +105,19 @@
                     </run-dependency>
                 </dependency>
                 <dependency>
-                    <code-name-base>org.netbeans.modules.editor.settings.storage</code-name-base>
+                    <code-name-base>org.netbeans.modules.editor.settings.lib</code-name-base>
                     <build-prerequisite/>
                     <compile-dependency/>
                     <run-dependency>
-                        <release-version>1</release-version>
                         <specification-version>1.49</specification-version>
                     </run-dependency>
                 </dependency>
                 <dependency>
-                    <code-name-base>org.netbeans.modules.editor.settings.lib</code-name-base>
+                    <code-name-base>org.netbeans.modules.editor.settings.storage</code-name-base>
                     <build-prerequisite/>
                     <compile-dependency/>
                     <run-dependency>
+                        <release-version>1</release-version>
                         <specification-version>1.49</specification-version>
                     </run-dependency>
                 </dependency>
@@ -189,7 +198,7 @@
                     </run-dependency>
                 </dependency>
                 <dependency>
-                    <code-name-base>org.openide.util.ui</code-name-base>
+                    <code-name-base>org.openide.util</code-name-base>
                     <build-prerequisite/>
                     <compile-dependency/>
                     <run-dependency>
@@ -197,19 +206,19 @@
                     </run-dependency>
                 </dependency>
                 <dependency>
-                    <code-name-base>org.openide.util</code-name-base>
+                    <code-name-base>org.openide.util.lookup</code-name-base>
                     <build-prerequisite/>
                     <compile-dependency/>
                     <run-dependency>
-                        <specification-version>9.3</specification-version>
+                        <specification-version>8.0</specification-version>
                     </run-dependency>
                 </dependency>
                 <dependency>
-                    <code-name-base>org.openide.util.lookup</code-name-base>
+                    <code-name-base>org.openide.util.ui</code-name-base>
                     <build-prerequisite/>
                     <compile-dependency/>
                     <run-dependency>
-                        <specification-version>8.0</specification-version>
+                        <specification-version>9.3</specification-version>
                     </run-dependency>
                 </dependency>
                 <dependency>
diff --git a/ide/editor.codetemplates/src/org/netbeans/lib/editor/codetemplates/AbbrevDetection.java b/ide/editor.codetemplates/src/org/netbeans/lib/editor/codetemplates/AbbrevDetection.java
index 8281dc1..d121d22 100644
--- a/ide/editor.codetemplates/src/org/netbeans/lib/editor/codetemplates/AbbrevDetection.java
+++ b/ide/editor.codetemplates/src/org/netbeans/lib/editor/codetemplates/AbbrevDetection.java
@@ -508,7 +508,7 @@ public final class AbbrevDetection implements DocumentListener, PropertyChangeLi
         op.waitLoaded();
         CodeTemplate ct = op.findByAbbreviation(abbrev.toString());
         if (ct != null) {
-            if (accept(ct, CodeTemplateManagerOperation.getTemplateFilters(component, abbrevStartOffset))) {
+            if (accept(ct, CodeTemplateManagerOperation.getTemplateFilters(component.getDocument(), abbrevStartOffset, abbrevStartOffset))) {
                 Document doc = component.getDocument();
                 sendUndoableEdit(doc, CloneableEditorSupport.BEGIN_COMMIT_GROUP);
                 try {
diff --git a/ide/editor.codetemplates/src/org/netbeans/lib/editor/codetemplates/CodeTemplateCompletionProvider.java b/ide/editor.codetemplates/src/org/netbeans/lib/editor/codetemplates/CodeTemplateCompletionProvider.java
index b9617af..e0ec7e9 100644
--- a/ide/editor.codetemplates/src/org/netbeans/lib/editor/codetemplates/CodeTemplateCompletionProvider.java
+++ b/ide/editor.codetemplates/src/org/netbeans/lib/editor/codetemplates/CodeTemplateCompletionProvider.java
@@ -19,29 +19,42 @@
 
 package org.netbeans.lib.editor.codetemplates;
 
+import java.io.IOException;
+import java.io.StringReader;
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
 import java.util.prefs.Preferences;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 import javax.swing.event.ChangeEvent;
 import javax.swing.event.ChangeListener;
 import javax.swing.text.AbstractDocument;
 import javax.swing.text.BadLocationException;
 import javax.swing.text.Document;
 import javax.swing.text.JTextComponent;
+import javax.swing.text.html.HTMLEditorKit;
+import javax.swing.text.html.parser.ParserDelegator;
 import org.netbeans.api.editor.mimelookup.MimeLookup;
 import org.netbeans.api.editor.mimelookup.MimePath;
 import org.netbeans.api.editor.mimelookup.MimeRegistration;
 import org.netbeans.api.editor.settings.SimpleValueNames;
 import org.netbeans.api.lexer.TokenHierarchy;
 import org.netbeans.api.lexer.TokenSequence;
+import org.netbeans.api.lsp.Completion;
 import org.netbeans.editor.BaseDocument;
 import org.netbeans.editor.Utilities;
 import org.netbeans.lib.editor.codetemplates.api.CodeTemplate;
 import org.netbeans.lib.editor.codetemplates.spi.CodeTemplateFilter;
+import org.netbeans.lib.editor.codetemplates.spi.CodeTemplateParameter;
 import org.netbeans.lib.editor.util.swing.DocumentUtilities;
 import org.netbeans.modules.editor.NbEditorUtilities;
 import org.netbeans.spi.editor.completion.CompletionItem;
@@ -50,6 +63,7 @@ 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.lsp.CompletionCollector;
 
 /**
  * Implemenation of the code template description.
@@ -70,7 +84,115 @@ public final class CodeTemplateCompletionProvider implements CompletionProvider
     private static boolean isAbbrevDisabled(JTextComponent component) {
         return org.netbeans.editor.Abbrev.isAbbrevDisabled(component);
     }
-    
+
+    @MimeRegistration(mimeType = "text/x-java", service = CompletionCollector.class)
+    public static class Collector implements CompletionCollector {
+
+        private static final String SELECTED_TEXT_VAR = "${0:$TM_SELECTED_TEXT}";
+        private static final Pattern SNIPPET_VAR_PATTERN = Pattern.compile("\\$\\{\\s*([-\\w]++)([^}]*)?}");
+        private static final Pattern SNIPPET_HINT_PATTERN = Pattern.compile("([-\\w]++)(?:\\s*=\\s*(\\\"([^\\\"]*)\\\"|\\S*))?");
+
+        @Override
+        public boolean collectCompletions(Document doc, int offset, Completion.Context context, Consumer<Completion> consumer) {
+            new Query().query(doc, offset, (ct, abbrevBased) -> {
+                String description = ct.getDescription();
+                if (description == null) {
+                    description = CodeTemplateApiPackageAccessor.get().getSingleLineText(ct);
+                }
+                String label = html2text(description.trim());
+                consumer.accept(CompletionCollector.newBuilder(label)
+                        .sortText(String.format("%04d%s", 1650, label))
+                        .documentation(() -> {
+                            StringBuffer sb = new StringBuffer("<html><pre>");
+                            ParametrizedTextParser.parseToHtml(sb, ct.getParametrizedText());
+                            sb.append("</pre>");
+                            return sb.toString();
+                        }).insertText(convert(ct.getParametrizedText()))
+                        .insertTextFormat(Completion.TextFormat.Snippet)
+                        .build());
+            });
+            return true;
+        }
+
+        private static String convert(String s) {
+            StringBuilder sb = new StringBuilder();
+            Matcher matcher = SNIPPET_VAR_PATTERN.matcher(s);
+            int idx = 0;
+            Map<String, Integer> placeholders = new HashMap<>();
+            Map<String, String> values = new HashMap<>();
+            Set<String> nonEditables = new HashSet<>();
+            AtomicInteger last = new AtomicInteger();
+            while (matcher.find(idx)) {
+                int start = matcher.start();
+                sb.append(s.substring(idx, start));
+                String name = matcher.group(1);
+                switch (name) {
+                    case CodeTemplateParameter.CURSOR_PARAMETER_NAME:
+                    case CodeTemplateParameter.NO_FORMAT_PARAMETER_NAME:
+                    case CodeTemplateParameter.NO_INDENT_PARAMETER_NAME:
+                        break;
+                    case CodeTemplateParameter.SELECTION_PARAMETER_NAME:
+                        sb.append(SELECTED_TEXT_VAR);
+                        break;
+                    default:
+                        String params = matcher.groupCount() > 1 ? matcher.group(2) : null;
+                        if (params == null || params.isEmpty()) {
+                            if (nonEditables.contains(name)) {
+                                sb.append(values.getOrDefault(name, ""));
+                            } else {
+                                sb.append('$').append(placeholders.computeIfAbsent(name, n -> last.incrementAndGet()));
+                            }
+                        } else {
+                            Map<String, String> hints = getHints(params);
+                            String defaultValue = hints.get(CodeTemplateParameter.DEFAULT_VALUE_HINT_NAME);
+                            if (defaultValue != null) {
+                                values.put(name, defaultValue);
+                            }
+                            if ("false".equalsIgnoreCase(hints.get(CodeTemplateParameter.EDITABLE_HINT_NAME))) {
+                                nonEditables.add(name);
+                                sb.append(values.getOrDefault(name, ""));
+                            } else {
+                                if (defaultValue != null) {
+                                    sb.append("${").append(placeholders.computeIfAbsent(name, n -> last.incrementAndGet())).append(':').append(defaultValue).append('}');
+                                } else {
+                                    sb.append('$').append(placeholders.computeIfAbsent(name, n -> last.incrementAndGet()));
+                                }
+                            }
+                        }
+                }
+                idx = matcher.end();
+            }
+            String tail = s.substring(idx);
+            return sb.append(tail.endsWith("\n") ? tail.substring(0, tail.length() - 1) : tail).toString();
+        }
+
+        private static Map<String, String> getHints(String text) {
+            Map<String, String> hint2Values = new HashMap<>();
+            Matcher matcher = SNIPPET_HINT_PATTERN.matcher(text);
+            int idx = 0;
+            while (matcher.find(idx)) {
+                hint2Values.put(matcher.group(1), matcher.groupCount() > 2 ? matcher.group(3) : matcher.groupCount() > 1 ? matcher.group(2) : null);
+                idx = matcher.end();
+            }
+            return hint2Values;
+        }
+
+        private static String html2text(String html) {
+            try {
+                StringBuilder sb = new StringBuilder();
+                new ParserDelegator().parse(new StringReader(html), new HTMLEditorKit.ParserCallback() {
+                    @Override
+                    public void handleText(char[] text, int pos) {
+                        sb.append(text);
+                    }
+                }, Boolean.TRUE);
+                return sb.toString();
+            } catch (IOException ex) {
+                return html;
+            }
+        }
+    }
+
     private static final class Query extends AsyncCompletionQuery
     implements ChangeListener {
 
@@ -138,6 +260,19 @@ public final class CodeTemplateCompletionProvider implements CompletionProvider
         }
         
         protected void query(CompletionResultSet resultSet, Document doc, int caretOffset) {
+            query(doc, caretOffset, (ct, abbrevBased) -> {
+                if (queryResult != null) {
+                    queryResult.add(new CodeTemplateCompletionItem(ct, abbrevBased));
+                }
+            });
+            if (queryResult != null) {
+                resultSet.addAllItems(queryResult);
+            }
+            resultSet.setAnchorOffset(queryAnchorOffset);
+            resultSet.finish();
+        }
+
+        private void query(Document doc, int caretOffset, BiConsumer<CodeTemplate, Boolean> consumer) {
             String langPath = null;
             String identifierBeforeCursor = null;
             if (doc instanceof AbstractDocument) {
@@ -171,7 +306,7 @@ public final class CodeTemplateCompletionProvider implements CompletionProvider
             queryCaretOffset = caretOffset;
             queryAnchorOffset = caretOffset - identifierBeforeCursor.length();
             if (langPath != null) {
-                String mimeType = DocumentUtilities.getMimeType(component);
+                String mimeType = DocumentUtilities.getMimeType(doc);
                 MimePath mimePath = mimeType == null ? MimePath.EMPTY : MimePath.get(mimeType);
                 Preferences prefs = MimeLookup.getLookup(mimePath).lookup(Preferences.class);
                 boolean ignoreCase = prefs.getBoolean(SimpleValueNames.COMPLETION_CASE_SENSITIVE, false);
@@ -181,25 +316,21 @@ public final class CodeTemplateCompletionProvider implements CompletionProvider
                 
                 Collection<? extends CodeTemplate> ctsPT = op.findByParametrizedText(identifierBeforeCursor, ignoreCase);
                 Collection<? extends CodeTemplate> ctsAb = op.findByAbbreviationPrefix(identifierBeforeCursor, ignoreCase);
-                Collection<? extends CodeTemplateFilter> filters = CodeTemplateManagerOperation.getTemplateFilters(component, queryAnchorOffset);
+                Collection<? extends CodeTemplateFilter> filters = CodeTemplateManagerOperation.getTemplateFilters(doc, queryAnchorOffset, queryAnchorOffset);
                 
                 queryResult = new ArrayList<CodeTemplateCompletionItem>(ctsPT.size() + ctsAb.size());
                 Set<String> abbrevs = new HashSet<String>(ctsPT.size() + ctsAb.size());
                 for (CodeTemplate ct : ctsPT) {
                     if (ct.getContexts() != null && ct.getContexts().size() > 0 && accept(ct, filters) && abbrevs.add(ct.getAbbreviation())) {
-                        queryResult.add(new CodeTemplateCompletionItem(ct, false));
+                        consumer.accept(ct, false);
                     }
                 }
                 for (CodeTemplate ct : ctsAb) {
                     if (ct.getContexts() != null && ct.getContexts().size() > 0 && accept(ct, filters) && abbrevs.add(ct.getAbbreviation())) {
-                        queryResult.add(new CodeTemplateCompletionItem(ct, true));
+                        consumer.accept(ct, true);
                     }
                 }
-                resultSet.addAllItems(queryResult);
             }
-            
-            resultSet.setAnchorOffset(queryAnchorOffset);
-            resultSet.finish();
         }
 
         public void stateChanged(ChangeEvent evt) {
diff --git a/ide/editor.codetemplates/src/org/netbeans/lib/editor/codetemplates/CodeTemplateManagerOperation.java b/ide/editor.codetemplates/src/org/netbeans/lib/editor/codetemplates/CodeTemplateManagerOperation.java
index 3233b97..edc4edb 100644
--- a/ide/editor.codetemplates/src/org/netbeans/lib/editor/codetemplates/CodeTemplateManagerOperation.java
+++ b/ide/editor.codetemplates/src/org/netbeans/lib/editor/codetemplates/CodeTemplateManagerOperation.java
@@ -251,14 +251,14 @@ public final class CodeTemplateManagerOperation
         return result;
     }
     
-    public static Collection<? extends CodeTemplateFilter> getTemplateFilters(JTextComponent component, int offset) {
-        MimePath mimeType = getFullMimePath(component.getDocument(), offset);
+    public static Collection<? extends CodeTemplateFilter> getTemplateFilters(Document doc, int startOffset, int endOffset) {
+        MimePath mimeType = getFullMimePath(doc, startOffset);
         Collection<? extends CodeTemplateFilter.Factory> filterFactories = 
             MimeLookup.getLookup(mimeType).lookupAll(CodeTemplateFilter.Factory.class);
         
         List<CodeTemplateFilter> result = new ArrayList<CodeTemplateFilter>(filterFactories.size());
         for (CodeTemplateFilter.Factory factory : filterFactories) {
-            result.add(factory.createFilter(component, offset));
+            result.add(factory.createFilter(doc, startOffset, endOffset));
         }
         return result;
     }
diff --git a/ide/editor.codetemplates/src/org/netbeans/lib/editor/codetemplates/SurroundWithFix.java b/ide/editor.codetemplates/src/org/netbeans/lib/editor/codetemplates/SurroundWithFix.java
index 7372be8..5cdb310 100644
--- a/ide/editor.codetemplates/src/org/netbeans/lib/editor/codetemplates/SurroundWithFix.java
+++ b/ide/editor.codetemplates/src/org/netbeans/lib/editor/codetemplates/SurroundWithFix.java
@@ -46,7 +46,7 @@ public class SurroundWithFix implements Fix {
             CodeTemplateManagerOperation op = CodeTemplateManagerOperation.get(component.getDocument(), component.getSelectionStart());
             if (op != null) {
                 op.waitLoaded();
-                Collection<? extends CodeTemplateFilter> filters = CodeTemplateManagerOperation.getTemplateFilters(component, component.getSelectionStart());
+                Collection<? extends CodeTemplateFilter> filters = CodeTemplateManagerOperation.getTemplateFilters(component.getDocument(), component.getSelectionStart(), component.getSelectionEnd());
                 for (CodeTemplate template : op.findSelectionTemplates()) {
                     // for surround-with use also templates that have no contexts.
                     // They are usually user-defined, see #118996.
diff --git a/ide/editor.codetemplates/src/org/netbeans/lib/editor/codetemplates/spi/CodeTemplateFilter.java b/ide/editor.codetemplates/src/org/netbeans/lib/editor/codetemplates/spi/CodeTemplateFilter.java
index 7e04f16..9ec76ad 100644
--- a/ide/editor.codetemplates/src/org/netbeans/lib/editor/codetemplates/spi/CodeTemplateFilter.java
+++ b/ide/editor.codetemplates/src/org/netbeans/lib/editor/codetemplates/spi/CodeTemplateFilter.java
@@ -20,6 +20,8 @@
 package org.netbeans.lib.editor.codetemplates.spi;
 
 import java.util.List;
+import javax.swing.JTextArea;
+import javax.swing.text.Document;
 import javax.swing.text.JTextComponent;
 import org.netbeans.lib.editor.codetemplates.api.CodeTemplate;
 import org.netbeans.spi.editor.mimelookup.MimeLocation;
@@ -56,6 +58,22 @@ public interface CodeTemplateFilter {
          * @return non-null code template filter instance.
          */
         CodeTemplateFilter createFilter(JTextComponent component, int offset);
+
+        /**
+         * Create code template filter for the given context.
+         *
+         * @param doc non-null document for which the filter is being created.
+         * @param startOffset &gt;=0 start offset for which the filter is being created.
+         * @param endOffset &gt;=startOffset end offset for which the filter is being created.
+         * @return non-null code template filter instance.
+         * @since 1.57
+         */
+        default CodeTemplateFilter createFilter(Document doc, int startOffset, int endOffset) {
+            JTextArea component = new JTextArea(doc);
+            component.setSelectionStart(startOffset);
+            component.setSelectionEnd(endOffset);
+            return createFilter(component, startOffset);
+        }
     }
     
     /**
diff --git a/java/java.editor/src/org/netbeans/modules/editor/java/JavaCodeTemplateFilter.java b/java/java.editor/src/org/netbeans/modules/editor/java/JavaCodeTemplateFilter.java
index bbfbe4f..c93e2b3 100644
--- a/java/java.editor/src/org/netbeans/modules/editor/java/JavaCodeTemplateFilter.java
+++ b/java/java.editor/src/org/netbeans/modules/editor/java/JavaCodeTemplateFilter.java
@@ -26,6 +26,7 @@ import java.util.EnumSet;
 import java.util.HashSet;
 import java.util.List;
 import java.util.concurrent.atomic.AtomicBoolean;
+import javax.swing.text.Document;
 import javax.swing.text.JTextComponent;
 import com.sun.source.tree.CaseTree;
 import com.sun.source.tree.ClassTree;
@@ -65,11 +66,9 @@ public class JavaCodeTemplateFilter implements CodeTemplateFilter {
     private Tree.Kind treeKindCtx = null;
     private String stringCtx = null;
     
-    private JavaCodeTemplateFilter(JTextComponent component, int offset) {
-        if (Utilities.isJavaContext(component, offset, true)) {
-            final int startOffset = offset;
-            final int endOffset = component.getSelectionStart() == offset ? component.getSelectionEnd() : -1;
-            final Source source = Source.create(component.getDocument());
+    private JavaCodeTemplateFilter(Document doc, int startOffset, int endOffset) {
+        if (Utilities.isJavaContext(doc, startOffset, true)) {
+            final Source source = Source.create(doc);
             if (source != null) {
                 final AtomicBoolean cancel = new AtomicBoolean();
                 BaseProgressUtils.runOffEventDispatchThread(() -> {
@@ -216,7 +215,12 @@ public class JavaCodeTemplateFilter implements CodeTemplateFilter {
         
         @Override
         public CodeTemplateFilter createFilter(JTextComponent component, int offset) {
-            return new JavaCodeTemplateFilter(component, offset);
+            return createFilter(component.getDocument(), offset, component.getSelectionStart() == offset ? component.getSelectionEnd() : -1);
+        }
+
+        @Override
+        public CodeTemplateFilter createFilter(Document doc, int startOffset, int endOffset) {
+            return new JavaCodeTemplateFilter(doc, startOffset, endOffset);
         }
 
         @Override
diff --git a/java/java.editor/src/org/netbeans/modules/editor/java/Utilities.java b/java/java.editor/src/org/netbeans/modules/editor/java/Utilities.java
index 4aaa7cf..ef66a8d 100644
--- a/java/java.editor/src/org/netbeans/modules/editor/java/Utilities.java
+++ b/java/java.editor/src/org/netbeans/modules/editor/java/Utilities.java
@@ -235,7 +235,10 @@ public final class Utilities {
     }        
     
     public static boolean isJavaContext(final JTextComponent component, final int offset, final boolean allowInStrings) {
-        Document doc = component.getDocument();
+        return isJavaContext(component.getDocument(), offset, allowInStrings);
+    }
+
+    public static boolean isJavaContext(final Document doc, final int offset, final boolean allowInStrings) {
         if (doc instanceof AbstractDocument) {
             ((AbstractDocument)doc).readLock();
         }
diff --git a/java/java.editor/src/org/netbeans/modules/java/editor/resources/DefaultAbbrevs.xml b/java/java.editor/src/org/netbeans/modules/java/editor/resources/DefaultAbbrevs.xml
index 0ed5b15..45d9e3c 100644
--- a/java/java.editor/src/org/netbeans/modules/java/editor/resources/DefaultAbbrevs.xml
+++ b/java/java.editor/src/org/netbeans/modules/java/editor/resources/DefaultAbbrevs.xml
@@ -58,18 +58,26 @@
     <codetemplate abbreviation="serr" contexts="BLOCK,CASE,LABELED_STATEMENT,DO_WHILE_LOOP,ENHANCED_FOR_LOOP,FOR_LOOP,IF,WHILE_LOOP,LAMBDA_EXPRESSION"><code><![CDATA[System.err.println("${cursor}");]]></code></codetemplate> 
     <codetemplate abbreviation="sout" contexts="BLOCK,CASE,LABELED_STATEMENT,DO_WHILE_LOOP,ENHANCED_FOR_LOOP,FOR_LOOP,IF,WHILE_LOOP,LAMBDA_EXPRESSION"><code><![CDATA[System.out.println("${cursor}");]]></code></codetemplate> 
     <codetemplate abbreviation="st"><code><![CDATA[${no-indent}static ]]></code></codetemplate> 
-    <codetemplate abbreviation="sw"><code><![CDATA[switch (${var instanceof="java.lang.Enum"}) {
-        case ${val completionInvoke}:
-            ${cursor}
-            break;
-        default:
-            throw new AssertionError();
-    }
-]]></code></codetemplate>
-    <codetemplate abbreviation="cs"><code><![CDATA[case ${what completionInvoke}:
-            ${cursor}
-            break;
-]]></code></codetemplate> 
+    <codetemplate abbreviation="sw" contexts="BLOCK,CASE,LABELED_STATEMENT,DO_WHILE_LOOP,ENHANCED_FOR_LOOP,FOR_LOOP,IF,WHILE_LOOP,LAMBDA_EXPRESSION">
+        <code>
+<![CDATA[switch (${var instanceof="java.lang.Enum"}) {
+    case ${val completionInvoke}:
+        ${selection}${cursor}
+        break;
+    default:
+        throw new AssertionError();
+}
+]]>
+        </code>
+    </codetemplate>
+    <codetemplate abbreviation="cs" contexts="CASE,SWITCH">
+        <code>
+<![CDATA[case ${what completionInvoke}:
+    ${selection}${cursor}
+    break;
+]]>
+        </code>
+    </codetemplate>
     <codetemplate abbreviation="tw"><code><![CDATA[throw ${exc instanceof="java.lang.Throwable" default="new IllegalStateException()"};]]></code></codetemplate>
     <codetemplate abbreviation="ise"><code><![CDATA[throw new IllegalStateException("${arg}");]]></code></codetemplate>
     <codetemplate abbreviation="iae"><code><![CDATA[throw new IllegalArgumentException("${arg}");]]></code></codetemplate>
@@ -79,9 +87,9 @@
     }
 ]]></code></codetemplate>
     <codetemplate abbreviation="do"><code><![CDATA[do {
-            ${cursor}
-        } while (${expr instanceof="boolean" default="true"});
-    ]]></code></codetemplate>
+        ${cursor}
+    } while (${expr instanceof="boolean" default="true"});
+]]></code></codetemplate>
 
     <codetemplate abbreviation="sy"><code><![CDATA[${no-indent}synchronized ]]></code></codetemplate> 
     <codetemplate abbreviation="tds"><code><![CDATA[Thread.dumpStack();]]></code></codetemplate> 
@@ -108,7 +116,7 @@
     <codetemplate abbreviation="for" contexts="BLOCK,CASE,LABELED_STATEMENT,DO_WHILE_LOOP,ENHANCED_FOR_LOOP,FOR_LOOP,IF,WHILE_LOOP,LAMBDA_EXPRESSION">
         <code>
 <![CDATA[for (int ${IDX newVarName default="i"} = 0; ${IDX} < ${MAX default="10"}; ${IDX}++) {
-${selection}${cursor}
+    ${selection}${cursor}
 }
 ]]>
         </code>
@@ -117,8 +125,8 @@ ${selection}${cursor}
     <codetemplate abbreviation="fori" contexts="BLOCK,CASE,LABELED_STATEMENT,DO_WHILE_LOOP,ENHANCED_FOR_LOOP,FOR_LOOP,IF,WHILE_LOOP,LAMBDA_EXPRESSION">
         <code>
 <![CDATA[for (int ${IDX newVarName default="idx"} = 0; ${IDX} < ${ARR array default="arr"}.length; ${IDX}++) {
-   ${TYPE rightSideType default="Object"} ${ELEM newVarName default="elem"} = ${TYPE_CAST cast default="" editable=false}${ARR}[${IDX}];
-${selection}${cursor}
+    ${TYPE rightSideType default="Object"} ${ELEM newVarName default="elem"} = ${TYPE_CAST cast default="" editable=false}${ARR}[${IDX}];
+    ${selection}${cursor}
 }
 ]]>
         </code>
@@ -126,8 +134,8 @@ ${selection}${cursor}
     <codetemplate abbreviation="forc" contexts="BLOCK,CASE,LABELED_STATEMENT,DO_WHILE_LOOP,ENHANCED_FOR_LOOP,FOR_LOOP,IF,WHILE_LOOP,LAMBDA_EXPRESSION">
         <code>
 <![CDATA[for (${IT_TYPE rightSideType type="java.util.Iterator" default="Iterator" editable=false} ${IT newVarName default="it"} = ${COL instanceof="java.util.Collection" default="col"}.iterator(); ${IT}.hasNext();) {
-   ${TYPE rightSideType default="Object"} ${ELEM newVarName default="elem"} = ${TYPE_CAST cast default="" editable=false}${IT}.next();
-${selection}${cursor}
+    ${TYPE rightSideType default="Object"} ${ELEM newVarName default="elem"} = ${TYPE_CAST cast default="" editable=false}${IT}.next();
+    ${selection}${cursor}
 }
 ]]>
         </code>
@@ -135,8 +143,8 @@ ${selection}${cursor}
     <codetemplate abbreviation="forl" contexts="BLOCK,CASE,LABELED_STATEMENT,DO_WHILE_LOOP,ENHANCED_FOR_LOOP,FOR_LOOP,IF,WHILE_LOOP,LAMBDA_EXPRESSION">
         <code>
 <![CDATA[for (int ${IDX newVarName default="idx"} = 0; ${IDX} < ${LIST instanceof="java.util.List" default="lst"}.size(); ${IDX}++) {
-   ${TYPE rightSideType default="Object"} ${ELEM newVarName default="elem"} = ${TYPE_CAST cast default="" editable=false}${LIST}.get(${IDX});
-${selection}${cursor}
+    ${TYPE rightSideType default="Object"} ${ELEM newVarName default="elem"} = ${TYPE_CAST cast default="" editable=false}${LIST}.get(${IDX});
+    ${selection}${cursor}
 }
 ]]>
         </code>
@@ -144,8 +152,8 @@ ${selection}${cursor}
     <codetemplate abbreviation="forv" contexts="BLOCK,CASE,LABELED_STATEMENT,DO_WHILE_LOOP,ENHANCED_FOR_LOOP,FOR_LOOP,IF,WHILE_LOOP,LAMBDA_EXPRESSION">
         <code>
 <![CDATA[for (int ${IDX newVarName default="idx"} = 0; ${IDX} < ${VECTOR instanceof="java.util.Vector" default="vct"}.size(); ${IDX}++) {
-   ${TYPE rightSideType default="Object"} ${ELEM newVarName default="elem"} = ${TYPE_CAST cast default="" editable=false}${VECTOR}.elementAt(${IDX});
-${selection}${cursor}
+    ${TYPE rightSideType default="Object"} ${ELEM newVarName default="elem"} = ${TYPE_CAST cast default="" editable=false}${VECTOR}.elementAt(${IDX});
+    ${selection}${cursor}
 }
 ]]>
         </code>
@@ -153,7 +161,7 @@ ${selection}${cursor}
     <codetemplate abbreviation="fore" contexts="BLOCK,CASE,LABELED_STATEMENT,DO_WHILE_LOOP,ENHANCED_FOR_LOOP,FOR_LOOP,IF,WHILE_LOOP,LAMBDA_EXPRESSION">
         <code>
 <![CDATA[for (${TYPE iterableElementType default="Object" editable=false} ${ELEM newVarName default="elem" ordering=2} : ${ITER iterable default="col" ordering=1}) {
-${selection}${cursor}
+    ${selection}${cursor}
 }
 ]]>
         </code>
@@ -162,7 +170,7 @@ ${selection}${cursor}
         <code>
 <![CDATA[for (${STR_TOK type="java.util.StringTokenizer" default="StringTokenizer" editable=false} ${TOKENIZER newVarName} = new ${STR_TOK}(${STRING instanceof="java.lang.String"}); ${TOKENIZER}.hasMoreTokens();) {
     String ${TOKEN default="token"} = ${TOKENIZER}.nextToken();
-${selection}${cursor}
+    ${selection}${cursor}
 }
 ]]>
         </code>
@@ -170,8 +178,8 @@ ${selection}${cursor}
     <codetemplate abbreviation="inst" contexts="BLOCK,CASE,LABELED_STATEMENT,DO_WHILE_LOOP,ENHANCED_FOR_LOOP,FOR_LOOP,IF,WHILE_LOOP,LAMBDA_EXPRESSION">
         <code>
 <![CDATA[if (${EXP instanceof="java.lang.Object" default="exp"} instanceof ${TYPE default="Object"}) {
-   ${TYPE} ${VAR newVarName default="obj"} = (${TYPE})${EXP};
-   ${cursor}
+    ${TYPE} ${VAR newVarName default="obj"} = (${TYPE})${EXP};
+    ${cursor}
 }
 ]]>
         </code>
@@ -184,8 +192,8 @@ ${selection}${cursor}
     <codetemplate abbreviation="whilen" contexts="BLOCK,CASE,LABELED_STATEMENT,DO_WHILE_LOOP,ENHANCED_FOR_LOOP,FOR_LOOP,IF,WHILE_LOOP,LAMBDA_EXPRESSION">
         <code>
 <![CDATA[while (${ENUM instanceof="java.util.Enumeration" default="en"}.hasMoreElements()) {
-   ${TYPE rightSideType default="Object"} ${ELEM newVarName default="elem"} = ${TYPE_CAST cast default="" editable=false} ${ENUM}.nextElement();
-${selection}${cursor}
+    ${TYPE rightSideType default="Object"} ${ELEM newVarName default="elem"} = ${TYPE_CAST cast default="" editable=false} ${ENUM}.nextElement();
+    ${selection}${cursor}
 }
 ]]>
         </code>
@@ -193,8 +201,8 @@ ${selection}${cursor}
     <codetemplate abbreviation="whileit" contexts="BLOCK,CASE,LABELED_STATEMENT,DO_WHILE_LOOP,ENHANCED_FOR_LOOP,FOR_LOOP,IF,WHILE_LOOP,LAMBDA_EXPRESSION">
         <code>
 <![CDATA[while (${IT instanceof="java.util.Iterator" default="it"}.hasNext()) {
-   ${TYPE rightSideType default="Object"} ${ELEM newVarName default="elem"} = ${TYPE_CAST cast default="" editable=false} ${IT}.next();
-${selection}${cursor}
+    ${TYPE rightSideType default="Object"} ${ELEM newVarName default="elem"} = ${TYPE_CAST cast default="" editable=false} ${IT}.next();
+    ${selection}${cursor}
 }
 ]]>
         </code>
@@ -202,7 +210,7 @@ ${selection}${cursor}
     <codetemplate abbreviation="iff" contexts="BLOCK,CASE,LABELED_STATEMENT,DO_WHILE_LOOP,ENHANCED_FOR_LOOP,FOR_LOOP,IF,WHILE_LOOP,LAMBDA_EXPRESSION" descriptionId="CT_iff">
         <code>
 <![CDATA[if (${EXP instanceof="boolean" default="true"}) {
-${selection}${cursor}
+    ${selection}${cursor}
 }
 ]]>
         </code>
@@ -210,7 +218,7 @@ ${selection}${cursor}
     <codetemplate abbreviation="ifelse" contexts="BLOCK,CASE,LABELED_STATEMENT,DO_WHILE_LOOP,ENHANCED_FOR_LOOP,FOR_LOOP,IF,WHILE_LOOP,LAMBDA_EXPRESSION" descriptionId="CT_ifelse">
         <code>
 <![CDATA[if (${EXP instanceof="boolean" default="true"}) {
-${selection}${cursor}
+    ${selection}${cursor}
 } else {
 }
 ]]>
@@ -219,7 +227,7 @@ ${selection}${cursor}
     <codetemplate abbreviation="whilexp" contexts="BLOCK,CASE,LABELED_STATEMENT,DO_WHILE_LOOP,ENHANCED_FOR_LOOP,FOR_LOOP,IF,WHILE_LOOP,LAMBDA_EXPRESSION">
         <code>
 <![CDATA[while (${EXP instanceof="boolean" default="true"}) { 
-${selection}${cursor}
+    ${selection}${cursor}
 }
 ]]>
         </code>
@@ -227,7 +235,7 @@ ${selection}${cursor}
     <codetemplate abbreviation="dowhile" contexts="BLOCK,CASE,LABELED_STATEMENT,DO_WHILE_LOOP,ENHANCED_FOR_LOOP,FOR_LOOP,IF,WHILE_LOOP,LAMBDA_EXPRESSION">
         <code>
 <![CDATA[do { 
-${selection}${cursor}
+    ${selection}${cursor}
 } while (${EXP instanceof="boolean" default="true"});
 ]]>
         </code>
@@ -235,9 +243,9 @@ ${selection}${cursor}
     <codetemplate abbreviation="runn" contexts="BLOCK,CASE,LABELED_STATEMENT">
         <code>
 <![CDATA[${RUNN_TYPE type="java.lang.Runnable" default="Runnable" editable=false} ${RUNN newVarName default="r"} = new ${RUNN_TYPE}() {
-   public void run() {
-${selection}${cursor}
-   }
+    public void run() {
+        ${selection}${cursor}
+    }
 };
 ]]>
         </code>
@@ -245,7 +253,7 @@ ${selection}${cursor}
     <codetemplate abbreviation="trycatch" contexts="BLOCK,CASE,LABELED_STATEMENT,DO_WHILE_LOOP,ENHANCED_FOR_LOOP,FOR_LOOP,IF,WHILE_LOOP,LAMBDA_EXPRESSION">
         <code>
 <![CDATA[try {
-${selection}${cursor}
+    ${selection}${cursor}
 } ${CATCH_STMTS uncaughtExceptionCatchStatements default="catch (Exception e) {}" editable=false}
 ]]>
         </code>
@@ -259,7 +267,7 @@ ${selection}${cursor}
     <codetemplate abbreviation="psvm" contexts="CLASS">
         <code>
 <![CDATA[public static void main(String[] args) {
-   ${cursor}
+    ${cursor}
 }
 ]]>
         </code>
@@ -268,7 +276,7 @@ ${selection}${cursor}
     <codetemplate abbreviation="main" contexts="CLASS">
         <code>
 <![CDATA[public static void main(String[] args) {
-   ${cursor}
+    ${cursor}
 }
 ]]>
         </code>
@@ -322,7 +330,7 @@ ${cursor}]]></code></codetemplate>
 <![CDATA[for (${entryType iterableElementType default="Map.Entry<Object, Object>" editable=false} ${entry newVarName default="en"} : ${map instanceof="java.util.Map" default="m"}.entrySet()) {
     ${KeyType rightSideType default="Object"} ${key newVarName default="key"} = ${entry}.getKey();
     ${ValType rightSideType default="Object"} ${val newVarName default="val"} = ${entry}.getValue();
-${selection}${cursor}
+    ${selection}${cursor}
 }]]>
          </code>
      </codetemplate>
diff --git a/java/java.lsp.server/nbproject/project.xml b/java/java.lsp.server/nbproject/project.xml
index 9e4f7a3..ce9ea03 100644
--- a/java/java.lsp.server/nbproject/project.xml
+++ b/java/java.lsp.server/nbproject/project.xml
@@ -139,6 +139,15 @@
                     </run-dependency>
                 </dependency>
                 <dependency>
+                    <code-name-base>org.netbeans.modules.editor.codetemplates</code-name-base>
+                    <build-prerequisite/>
+                    <compile-dependency/>
+                    <run-dependency>
+                        <release-version>1</release-version>
+                        <specification-version>1.56</specification-version>
+                    </run-dependency>
+                </dependency>
+                <dependency>
                     <code-name-base>org.netbeans.modules.editor.document</code-name-base>
                     <build-prerequisite/>
                     <compile-dependency/>
diff --git a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/ExtractSuperclassOrInterfaceRefactoring.java b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/ExtractSuperclassOrInterfaceRefactoring.java
index 970f76a..89700a9 100644
--- a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/ExtractSuperclassOrInterfaceRefactoring.java
+++ b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/ExtractSuperclassOrInterfaceRefactoring.java
@@ -70,7 +70,7 @@ import org.openide.util.lookup.ServiceProvider;
  * @author Dusan Balek
  */
 @ServiceProvider(service = CodeActionsProvider.class, position = 170)
-public class ExtractSuperclassOrInterfaceRefactoring extends CodeRefactoring {
+public final class ExtractSuperclassOrInterfaceRefactoring extends CodeRefactoring {
 
     private static final String EXTRACT_SUPERCLASS_REFACTORING_COMMAND =  "java.refactor.extract.superclass";
     private static final String EXTRACT_INTERFACE_REFACTORING_COMMAND =  "java.refactor.extract.interface";
@@ -81,8 +81,8 @@ public class ExtractSuperclassOrInterfaceRefactoring extends CodeRefactoring {
 
     @Override
     @NbBundle.Messages({
-        "DN_ExtractSuperclass= Extract Superclass...",
-        "DN_ExtractInterface= Extract Interface...",
+        "DN_ExtractSuperclass=Extract Superclass...",
+        "DN_ExtractInterface=Extract Interface...",
     })
     public List<CodeAction> getCodeActions(ResultIterator resultIterator, CodeActionParams params) throws Exception {
         List<String> only = params.getContext().getOnly();
@@ -181,7 +181,7 @@ public class ExtractSuperclassOrInterfaceRefactoring extends CodeRefactoring {
                 }
             });
         } else {
-            client.logMessage(new MessageParams(MessageType.Error, String.format("Illegal number of arguments received for command: %s", command)));
+            client.showMessage(new MessageParams(MessageType.Error, String.format("Illegal number of arguments received for command: %s", command)));
         }
         return CompletableFuture.completedFuture(true);
     }
@@ -232,7 +232,7 @@ public class ExtractSuperclassOrInterfaceRefactoring extends CodeRefactoring {
             refactoring.getContext().add(JavaRefactoringUtils.getClasspathInfoFor(file));
             client.applyEdit(new ApplyWorkspaceEditParams(perform(refactoring, EXTRACT_SUPERCLASS_REFACTORING_COMMAND.equals(command) ? "Extract Superclass" : "Extract Interface")));
         } catch (Exception ex) {
-            client.logMessage(new MessageParams(MessageType.Error, ex.getLocalizedMessage()));
+            client.showMessage(new MessageParams(MessageType.Error, ex.getLocalizedMessage()));
         }
     }
 }
diff --git a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/MoveRefactoring.java b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/MoveRefactoring.java
index 72160e9..9aa18d7 100644
--- a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/MoveRefactoring.java
+++ b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/MoveRefactoring.java
@@ -69,7 +69,7 @@ import org.openide.util.lookup.ServiceProvider;
  * @author Dusan Balek
  */
 @ServiceProvider(service = CodeActionsProvider.class, position = 160)
-public class MoveRefactoring extends CodeRefactoring {
+public final class MoveRefactoring extends CodeRefactoring {
 
     private static final String MOVE_REFACTORING_KIND = "refactor.move";
     private static final String MOVE_REFACTORING_COMMAND =  "java.refactor.move";
@@ -80,7 +80,7 @@ public class MoveRefactoring extends CodeRefactoring {
 
     @Override
     @NbBundle.Messages({
-        "DN_Move= Move...",
+        "DN_Move=Move...",
     })
     public List<CodeAction> getCodeActions(ResultIterator resultIterator, CodeActionParams params) throws Exception {
         List<String> only = params.getContext().getOnly();
@@ -181,7 +181,7 @@ public class MoveRefactoring extends CodeRefactoring {
                 throw new IllegalArgumentException(String.format("Illegal number of arguments received for command: %s", command));
             }
         } catch (Exception ex) {
-            client.logMessage(new MessageParams(MessageType.Error, ex.getLocalizedMessage()));
+            client.showMessage(new MessageParams(MessageType.Error, ex.getLocalizedMessage()));
         }
         return CompletableFuture.completedFuture(true);
     }
@@ -206,7 +206,7 @@ public class MoveRefactoring extends CodeRefactoring {
             }
             client.applyEdit(new ApplyWorkspaceEditParams(perform(refactoring, "Move")));
         } catch (Exception ex) {
-            client.logMessage(new MessageParams(MessageType.Error, ex.getLocalizedMessage()));
+            client.showMessage(new MessageParams(MessageType.Error, ex.getLocalizedMessage()));
         }
     }
 
diff --git a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/PullUpRefactoring.java b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/PullUpRefactoring.java
index 2beb73e..b146688 100644
--- a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/PullUpRefactoring.java
+++ b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/PullUpRefactoring.java
@@ -67,7 +67,7 @@ import org.openide.util.lookup.ServiceProvider;
  * @author Dusan Balek
  */
 @ServiceProvider(service = CodeActionsProvider.class, position = 180)
-public class PullUpRefactoring extends CodeRefactoring {
+public final class PullUpRefactoring extends CodeRefactoring {
 
     private static final String PULL_UP_REFACTORING_KIND = "refactor.pull.up";
     private static final String PULL_UP_REFACTORING_COMMAND =  "java.refactor.pull.up";
@@ -77,7 +77,7 @@ public class PullUpRefactoring extends CodeRefactoring {
 
     @Override
     @NbBundle.Messages({
-        "DN_PullUp= Pull Up...",
+        "DN_PullUp=Pull Up...",
     })
     public List<CodeAction> getCodeActions(ResultIterator resultIterator, CodeActionParams params) throws Exception {
         List<String> only = params.getContext().getOnly();
@@ -164,7 +164,7 @@ public class PullUpRefactoring extends CodeRefactoring {
                 throw new IllegalArgumentException(String.format("Illegal number of arguments received for command: %s", command));
             }
         } catch (Exception ex) {
-            client.logMessage(new MessageParams(MessageType.Error, ex.getLocalizedMessage()));
+            client.showMessage(new MessageParams(MessageType.Error, ex.getLocalizedMessage()));
         }
         return CompletableFuture.completedFuture(true);
     }
@@ -206,7 +206,7 @@ public class PullUpRefactoring extends CodeRefactoring {
                 }
             }, true);
         } catch (Exception ex) {
-            client.logMessage(new MessageParams(MessageType.Error, ex.getLocalizedMessage()));
+            client.showMessage(new MessageParams(MessageType.Error, ex.getLocalizedMessage()));
         }
         return members;
     }
@@ -238,7 +238,7 @@ public class PullUpRefactoring extends CodeRefactoring {
             refactoring.getContext().add(JavaRefactoringUtils.getClasspathInfoFor(file));
             client.applyEdit(new ApplyWorkspaceEditParams(perform(refactoring, "PullUp")));
         } catch (Exception ex) {
-            client.logMessage(new MessageParams(MessageType.Error, ex.getLocalizedMessage()));
+            client.showMessage(new MessageParams(MessageType.Error, ex.getLocalizedMessage()));
         }
     }
 
diff --git a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/PushDownRefactoring.java b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/PushDownRefactoring.java
index edb4500..591f071 100644
--- a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/PushDownRefactoring.java
+++ b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/PushDownRefactoring.java
@@ -28,6 +28,7 @@ import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
+import java.util.EnumSet;
 import java.util.List;
 import java.util.Set;
 import java.util.concurrent.CompletableFuture;
@@ -41,6 +42,7 @@ import org.eclipse.lsp4j.CodeActionKind;
 import org.eclipse.lsp4j.CodeActionParams;
 import org.eclipse.lsp4j.MessageParams;
 import org.eclipse.lsp4j.MessageType;
+import org.netbeans.api.java.source.ClassIndex;
 import org.netbeans.api.java.source.ClasspathInfo;
 import org.netbeans.api.java.source.CompilationController;
 import org.netbeans.api.java.source.CompilationInfo;
@@ -61,7 +63,7 @@ import org.openide.util.lookup.ServiceProvider;
  * @author Dusan Balek
  */
 @ServiceProvider(service = CodeActionsProvider.class, position = 190)
-public class PushDownRefactoring extends CodeRefactoring {
+public final class PushDownRefactoring extends CodeRefactoring {
 
     private static final String PUSH_DOWN_REFACTORING_KIND = "refactor.push.down";
     private static final String PUSH_DOWN_REFACTORING_COMMAND =  "java.refactor.push.down";
@@ -71,7 +73,7 @@ public class PushDownRefactoring extends CodeRefactoring {
 
     @Override
     @NbBundle.Messages({
-        "DN_PushDown= Push Down...",
+        "DN_PushDown=Push Down...",
     })
     public List<CodeAction> getCodeActions(ResultIterator resultIterator, CodeActionParams params) throws Exception {
         List<String> only = params.getContext().getOnly();
@@ -100,6 +102,11 @@ public class PushDownRefactoring extends CodeRefactoring {
         if (!(element instanceof TypeElement)) {
             return Collections.emptyList();
         }
+        ElementHandle<TypeElement> eh = ElementHandle.create((TypeElement) element);
+        Set<FileObject> resources = info.getClasspathInfo().getClassIndex().getResources(eh, EnumSet.of(ClassIndex.SearchKind.IMPLEMENTORS), EnumSet.of(ClassIndex.SearchScope.SOURCE));
+        if (resources.isEmpty()) {
+            return Collections.emptyList();
+        }
         List<QuickPickItem> members = new ArrayList<>();
         for (Element m: element.getEnclosedElements()) {
             if (m.getKind() == ElementKind.CONSTRUCTOR || m.getKind() == ElementKind.STATIC_INIT || m.getKind() == ElementKind.INSTANCE_INIT) {
@@ -146,7 +153,7 @@ public class PushDownRefactoring extends CodeRefactoring {
                 throw new IllegalArgumentException(String.format("Illegal number of arguments received for command: %s", command));
             }
         } catch (Exception ex) {
-            client.logMessage(new MessageParams(MessageType.Error, ex.getLocalizedMessage()));
+            client.showMessage(new MessageParams(MessageType.Error, ex.getLocalizedMessage()));
         }
         return CompletableFuture.completedFuture(true);
     }
@@ -174,7 +181,7 @@ public class PushDownRefactoring extends CodeRefactoring {
             refactoring.getContext().add(JavaRefactoringUtils.getClasspathInfoFor(file));
             client.applyEdit(new ApplyWorkspaceEditParams(perform(refactoring, "PushDown")));
         } catch (Exception ex) {
-            client.logMessage(new MessageParams(MessageType.Error, ex.getLocalizedMessage()));
+            client.showMessage(new MessageParams(MessageType.Error, ex.getLocalizedMessage()));
         }
     }
 
diff --git a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/SurroundWithHint.java b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/SurroundWithHint.java
new file mode 100644
index 0000000..904815d
--- /dev/null
+++ b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/SurroundWithHint.java
@@ -0,0 +1,329 @@
+/*
+ * 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.java.lsp.server.protocol;
+
+import com.sun.source.tree.BlockTree;
+import com.sun.source.tree.IdentifierTree;
+import com.sun.source.tree.StatementTree;
+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 com.sun.source.util.Trees;
+import java.io.IOException;
+import java.io.StringReader;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.ElementKind;
+import javax.lang.model.element.VariableElement;
+import javax.lang.model.type.TypeMirror;
+import javax.swing.text.Document;
+import javax.swing.text.html.HTMLEditorKit;
+import javax.swing.text.html.parser.ParserDelegator;
+import org.eclipse.lsp4j.CodeAction;
+import org.eclipse.lsp4j.CodeActionKind;
+import org.eclipse.lsp4j.CodeActionParams;
+import org.eclipse.lsp4j.Range;
+import org.eclipse.lsp4j.TextEdit;
+import org.eclipse.lsp4j.WorkspaceEdit;
+import org.netbeans.api.editor.mimelookup.MimeLookup;
+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.support.ErrorAwareTreePathScanner;
+import org.netbeans.lib.editor.codetemplates.api.CodeTemplate;
+import org.netbeans.lib.editor.codetemplates.api.CodeTemplateManager;
+import org.netbeans.lib.editor.codetemplates.spi.CodeTemplateFilter;
+import org.netbeans.lib.editor.codetemplates.spi.CodeTemplateParameter;
+import org.netbeans.lib.editor.util.swing.DocumentUtilities;
+import org.netbeans.modules.java.lsp.server.Utils;
+import org.netbeans.modules.parsing.api.ResultIterator;
+import org.openide.util.NbBundle;
+import org.openide.util.lookup.ServiceProvider;
+
+/**
+ *
+ * @author Dusan Balek
+ */
+@ServiceProvider(service = CodeActionsProvider.class, position = 80)
+public final class SurroundWithHint extends CodeActionsProvider {
+
+    private static final String COMMAND_INSERT_SNIPPET = "editor.action.insertSnippet";
+    private static final String DOTS = "...";
+    private static final String SNIPPET = "snippet";
+    private static final String SELECTION_VAR = "${selection}";
+    private static final String SELECTED_TEXT_VAR = "${0:$TM_SELECTED_TEXT}";
+    private static final Pattern SNIPPET_VAR_PATTERN = Pattern.compile("\\$\\{\\s*([-\\w]++)([^}]*)?}");
+    private static final Pattern SNIPPET_HINT_PATTERN = Pattern.compile("([-\\w]++)(?:\\s*=\\s*(\\\"([^\\\"]*)\\\"|\\S*))?");
+    private static final Set<String> TO_FILTER = Collections.singleton("fcom");
+
+    @Override
+    @NbBundle.Messages({
+        "DN_SurroundWith=Surround with \"{0}\"",
+    })
+    public List<CodeAction> getCodeActions(ResultIterator resultIterator, CodeActionParams params) throws Exception {
+        CompilationController info = CompilationController.get(resultIterator.getParserResult());
+        if (info == null) {
+            return Collections.emptyList();
+        }
+        info.toPhase(JavaSource.Phase.ELEMENTS_RESOLVED);
+        int startOffset = getOffset(info, params.getRange().getStart());
+        int endOffset = getOffset(info, params.getRange().getEnd());
+        if (startOffset >= endOffset) {
+            return Collections.emptyList();
+        }
+        Document doc;
+        try {
+            doc = info.getDocument();
+        } catch (IOException ex) {
+            return Collections.emptyList();
+        }
+        Collection<? extends CodeTemplateFilter> filters = getTemplateFilters(doc, startOffset, endOffset);
+        List<CodeAction> codeActions = new ArrayList<>();
+        for (CodeTemplate codeTemplate : CodeTemplateManager.get(doc).getCodeTemplates()) {
+            String parametrizedText = codeTemplate.getParametrizedText();
+            if (parametrizedText.toLowerCase().contains(SELECTION_VAR) && !TO_FILTER.contains(codeTemplate.getAbbreviation())) {
+                if (codeTemplate.getContexts() == null || codeTemplate.getContexts().isEmpty() || accept(codeTemplate, filters)) {
+                    String text = convert(codeTemplate.getParametrizedText(), null);
+                    String label = html2text(codeTemplate.getDescription());
+                    if (label == null) {
+                        int idx = text.indexOf('\n');
+                        label = idx < 0 ? text : text.substring(0, idx) + DOTS;
+                    }
+                    StringBuilder sb = new StringBuilder();
+                    AtomicInteger last = new AtomicInteger();
+                    List<TextEdit> edits = updateSelection(info, startOffset, endOffset, text, sb, last);
+                    String snippet = convert(codeTemplate.getParametrizedText(), last);
+                    if (sb.length() > 0) {
+                        snippet = sb.append(snippet).toString();
+                    }
+                    CodeAction codeAction = createCodeAction(Bundle.DN_SurroundWith(label), CodeActionKind.RefactorRewrite, COMMAND_INSERT_SNIPPET, Collections.singletonMap(SNIPPET, snippet));
+                    if (!edits.isEmpty()) {
+                        codeAction.setEdit(new WorkspaceEdit(Collections.singletonMap(params.getTextDocument().getUri(), edits)));
+                    }
+                    codeActions.add(codeAction);
+                }
+            }
+        }
+        return codeActions;
+    }
+
+    @Override
+    public Set<String> getCommands() {
+        return Collections.emptySet();
+    }
+
+    @Override
+    public CompletableFuture<Object> processCommand(NbCodeLanguageClient client, String command, List<Object> arguments) {
+        return CompletableFuture.completedFuture(false);
+    }
+
+    private static Collection<? extends CodeTemplateFilter> getTemplateFilters(Document doc, int startOffset, int endOffset) {
+        String mimeType = DocumentUtilities.getMimeType(doc);
+        Collection<? extends CodeTemplateFilter.Factory> filterFactories = MimeLookup.getLookup(mimeType).lookupAll(CodeTemplateFilter.Factory.class);
+        List<CodeTemplateFilter> result = new ArrayList<>(filterFactories.size());
+        for (CodeTemplateFilter.Factory factory : filterFactories) {
+            result.add(factory.createFilter(doc, startOffset, endOffset));
+        }
+        return result;
+    }
+
+    private static boolean accept(CodeTemplate template, Collection<? extends CodeTemplateFilter> filters) {
+        for(CodeTemplateFilter filter : filters) {
+            if (!filter.accept(template)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    private static String convert(String s, AtomicInteger last) {
+        StringBuilder sb = new StringBuilder();
+        Matcher matcher = SNIPPET_VAR_PATTERN.matcher(s);
+        int idx = 0;
+        Map<String, Integer> placeholders = new HashMap<>();
+        Map<String, String> values = new HashMap<>();
+        Set<String> nonEditables = new HashSet<>();
+        while (matcher.find(idx)) {
+            int start = matcher.start();
+            sb.append(s.substring(idx, start));
+            String name = matcher.group(1);
+            switch (name) {
+                case CodeTemplateParameter.CURSOR_PARAMETER_NAME:
+                case CodeTemplateParameter.NO_FORMAT_PARAMETER_NAME:
+                case CodeTemplateParameter.NO_INDENT_PARAMETER_NAME:
+                    break;
+                case CodeTemplateParameter.SELECTION_PARAMETER_NAME:
+                    if (last != null) {
+                        sb.append(SELECTED_TEXT_VAR);
+                    }
+                    break;
+                default:
+                    String params = matcher.groupCount() > 1 ? matcher.group(2) : null;
+                    if (params == null || params.isEmpty()) {
+                        if (last == null || nonEditables.contains(name)) {
+                            sb.append(values.getOrDefault(name, ""));
+                        } else {
+                            sb.append('$').append(placeholders.computeIfAbsent(name, n -> last.incrementAndGet()));
+                        }
+                    } else {
+                        Map<String, String> hints = getHints(params);
+                        String defaultValue = hints.get(CodeTemplateParameter.DEFAULT_VALUE_HINT_NAME);
+                        if (defaultValue != null) {
+                            values.put(name, defaultValue);
+                        }
+                        if ("false".equalsIgnoreCase(hints.get(CodeTemplateParameter.EDITABLE_HINT_NAME))) {
+                            nonEditables.add(name);
+                            sb.append(values.getOrDefault(name, ""));
+                        } else {
+                            if (defaultValue != null) {
+                                if (last == null) {
+                                    sb.append(defaultValue);
+                                } else {
+                                    sb.append("${").append(placeholders.computeIfAbsent(name, n -> last.incrementAndGet())).append(':').append(defaultValue).append('}');
+                                }
+                            } else if (last == null) {
+                                sb.append(values.getOrDefault(name, ""));
+                            } else {
+                                sb.append('$').append(placeholders.computeIfAbsent(name, n -> last.incrementAndGet()));
+                            }
+                        }
+                    }
+            }
+            idx = matcher.end();
+        }
+        String tail = s.substring(idx);
+        return sb.append(tail.endsWith("\n") ? tail.substring(0, tail.length() - 1) : tail).toString();
+    }
+
+    private static Map<String, String> getHints(String text) {
+        Map<String, String> hint2Values = new HashMap<>();
+        Matcher matcher = SNIPPET_HINT_PATTERN.matcher(text);
+        int idx = 0;
+        while (matcher.find(idx)) {
+            hint2Values.put(matcher.group(1), matcher.groupCount() > 2 ? matcher.group(3) : matcher.groupCount() > 1 ? matcher.group(2) : null);
+            idx = matcher.end();
+        }
+        return hint2Values;
+    }
+
+    private static String html2text(String html) throws IOException {
+        if (html == null) {
+            return html;
+        }
+        StringBuilder sb = new StringBuilder();
+        new ParserDelegator().parse(new StringReader(html), new HTMLEditorKit.ParserCallback() {
+            @Override
+            public void handleText(char[] text, int pos) {
+                sb.append(text);
+            }
+        }, Boolean.TRUE);
+        return sb.toString();
+    }
+
+    private static List<TextEdit> updateSelection(CompilationInfo info, int startOffset, int endOffset, String templateText, StringBuilder sb, AtomicInteger last) {
+        List<TextEdit> edits = new ArrayList<>();
+        TreeUtilities tu = info.getTreeUtilities();
+        StatementTree stat = tu.parseStatement(templateText, null);
+        EnumSet<Tree.Kind> kinds = EnumSet.of(Tree.Kind.BLOCK, Tree.Kind.DO_WHILE_LOOP,
+                Tree.Kind.ENHANCED_FOR_LOOP, Tree.Kind.FOR_LOOP, Tree.Kind.IF, Tree.Kind.SYNCHRONIZED,
+                Tree.Kind.TRY, Tree.Kind.WHILE_LOOP);
+        if (stat != null && kinds.contains(stat.getKind())) {
+            TreePath treePath = tu.pathFor(startOffset);
+            Tree tree = treePath.getLeaf();
+            if (tree.getKind() == Tree.Kind.BLOCK && tree == tu.pathFor(endOffset).getLeaf()) {
+                Trees trees = info.getTrees();
+                SourcePositions sp = trees.getSourcePositions();
+                Map<VariableElement, VariableTree> vars = new HashMap<>();
+                LinkedList<VariableTree> varList = new LinkedList<>();
+                ErrorAwareTreePathScanner scanner = new ErrorAwareTreePathScanner() {
+                    private int cnt = 0;
+                    @Override
+                    public Object visitIdentifier(IdentifierTree node, Object p) {
+                        Element e = trees.getElement(getCurrentPath());
+                        VariableTree var;
+                        if (e != null && (var = vars.remove(e)) != null) {
+                            sb.append(var.getType()).append(' ').append(var.getName());
+                            TypeMirror tm = ((VariableElement)e).asType();
+                            switch(tm.getKind()) {
+                                case ARRAY:
+                                case DECLARED:
+                                    sb.append(" = ${").append(last.incrementAndGet()).append(':').append("\"null\"}");
+                                    break;
+                                case BOOLEAN:
+                                    sb.append(" = ${").append(last.incrementAndGet()).append(':').append("\"false\"}");
+                                    break;
+                                case BYTE:
+                                case CHAR:
+                                case DOUBLE:
+                                case FLOAT:
+                                case INT:
+                                case LONG:
+                                case SHORT:
+                                    sb.append(" = ${").append(last.incrementAndGet()).append(':').append("\"0\"}");
+                                    break;
+                            }
+                            sb.append(";\n"); //NOI18N
+                        }
+                        return null;
+                    }
+                };
+                for (StatementTree st : ((BlockTree)tree).getStatements()) {
+                    if (sp.getStartPosition(info.getCompilationUnit(), st) >= startOffset) {
+                        if (sp.getEndPosition(info.getCompilationUnit(), st) <= endOffset) {
+                            if (st.getKind() == Tree.Kind.VARIABLE) {
+                                Element e = trees.getElement(new TreePath(treePath, st));
+                                if (e != null && e.getKind() == ElementKind.LOCAL_VARIABLE) {
+                                    vars.put((VariableElement)e, (VariableTree)st);
+                                    varList.addFirst((VariableTree)st);
+                                }
+                            }
+                        } else {
+                            scanner.scan(new TreePath(treePath, st), null);
+                        }
+                    }
+                }
+                Collection<VariableTree> vals = vars.values();
+                for (VariableTree var : varList) {
+                    if (!vals.contains(var)) {
+                        int start = (int) sp.getStartPosition(info.getCompilationUnit(), var.getType());
+                        int[] span = tu.findNameSpan(var);
+                        int end = span != null ? span[0] : (int) sp.getEndPosition(info.getCompilationUnit(), var.getType());
+                        edits.add(new TextEdit(new Range(Utils.createPosition(info.getCompilationUnit().getLineMap(), start), Utils.createPosition(info.getCompilationUnit().getLineMap(), end)), ""));
+                    }
+                }
+            }
+        }
+        return edits;
+    }
+}
diff --git a/java/java.lsp.server/test/unit/src/org/netbeans/modules/java/lsp/server/protocol/ServerTest.java b/java/java.lsp.server/test/unit/src/org/netbeans/modules/java/lsp/server/protocol/ServerTest.java
index 7cfdb35..5b9e704 100644
--- a/java/java.lsp.server/test/unit/src/org/netbeans/modules/java/lsp/server/protocol/ServerTest.java
+++ b/java/java.lsp.server/test/unit/src/org/netbeans/modules/java/lsp/server/protocol/ServerTest.java
@@ -19,6 +19,7 @@
 package org.netbeans.modules.java.lsp.server.protocol;
 
 import com.google.gson.Gson;
+import com.google.gson.JsonObject;
 import com.google.gson.JsonParser;
 import java.beans.PropertyChangeListener;
 import java.io.File;
@@ -4945,6 +4946,84 @@ public class ServerTest extends NbTestCase {
         }
     }
 
+    public void testSurroundWith() throws Exception {
+        File src = new File(getWorkDir(), "Test.java");
+        src.getParentFile().mkdirs();
+        String code = "public class Test {\n" +
+                      "    public static void main(String[] args) {\n" +
+                      "        System.out.println(\"Hello World\");\n" +
+                      "    }\n" +
+                      "}\n";
+        try (Writer w = new FileWriter(src)) {
+            w.write(code);
+        }
+
+        List<Diagnostic>[] diags = new List[1];
+        Launcher<LanguageServer> serverLauncher = LSPLauncher.createClientLauncher(new LspClient() {
+            @Override
+            public void telemetryEvent(Object arg0) {
+                throw new UnsupportedOperationException("Not supported yet.");
+            }
+
+            @Override
+            public void publishDiagnostics(PublishDiagnosticsParams params) {
+                synchronized (diags) {
+                    diags[0] = params.getDiagnostics();
+                    diags.notifyAll();
+                }
+            }
+
+            @Override
+            public void showMessage(MessageParams arg0) {
+            }
+
+            @Override
+            public CompletableFuture<MessageActionItem> showMessageRequest(ShowMessageRequestParams arg0) {
+                throw new UnsupportedOperationException("Not supported yet.");
+            }
+
+            @Override
+            public void logMessage(MessageParams arg0) {
+                throw new UnsupportedOperationException("Not supported yet.");
+            }
+
+            @Override
+            public CompletableFuture<ApplyWorkspaceEditResponse> applyEdit(ApplyWorkspaceEditParams params) {
+                throw new UnsupportedOperationException("Not supported yet.");
+            }
+
+        }, client.getInputStream(), client.getOutputStream());
+        serverLauncher.startListening();
+        LanguageServer server = serverLauncher.getRemoteProxy();
+        server.initialize(new InitializeParams()).get();
+        String uri = src.toURI().toString();
+        server.getTextDocumentService().didOpen(new DidOpenTextDocumentParams(new TextDocumentItem(uri, "java", 0, code)));
+        synchronized (diags) {
+            while (diags[0] == null) {
+                try {
+                    diags.wait();
+                } catch (InterruptedException ex) {
+                }
+            }
+        }
+        VersionedTextDocumentIdentifier id = new VersionedTextDocumentIdentifier(src.toURI().toString(), 1);
+        List<Either<Command, CodeAction>> codeActions = server.getTextDocumentService().codeAction(new CodeActionParams(id, new Range(new Position(2, 8), new Position(2, 42)), new CodeActionContext(diags[0]))).get();
+        Optional<CodeAction> surroundWith =
+                codeActions.stream()
+                           .filter(Either::isRight)
+                           .map(Either::getRight)
+                           .filter(a -> a.getTitle().startsWith(Bundle.DN_SurroundWith("do { ...")))
+                           .findAny();
+        assertTrue(surroundWith.isPresent());
+        Command command = surroundWith.get().getCommand();
+        assertEquals("editor.action.insertSnippet", command.getCommand());
+        assertEquals(1, command.getArguments().size());
+        JsonObject obj = (JsonObject) command.getArguments().get(0);
+        assertEquals("do { \n" +
+                     "    ${0:$TM_SELECTED_TEXT}\n" +
+                     "} while (${1:true});", obj.getAsJsonPrimitive("snippet").getAsString());
+    }
+
     public void testNoErrorAndHintsFor() throws Exception {
         File src = new File(getWorkDir(), "Test.java");
         src.getParentFile().mkdirs();

---------------------------------------------------------------------
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