From 5f4bd3b2ca7be84230141281e1e3cb50801b18ae Mon Sep 17 00:00:00 2001 From: Hope Hadfield Date: Thu, 17 Aug 2023 10:38:49 -0400 Subject: [PATCH] Introduce new snippet templates with appropriate context Signed-off-by: Hope Hadfield --- .../SnippetCompletionProposal.java | 51 +++++++++++++++---- .../internal/contentassist/SnippetUtils.java | 7 ++- .../template/java/CodeSnippetTemplate.java | 38 +++++++++++++- .../corext/template/java/JavaContextType.java | 6 +-- .../java/JavaContextTypeRegistry.java | 14 +++-- .../handlers/CompletionHandlerTest.java | 4 +- 6 files changed, 98 insertions(+), 22 deletions(-) diff --git a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/contentassist/SnippetCompletionProposal.java b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/contentassist/SnippetCompletionProposal.java index a5fe5ad9e6..a81f745e04 100644 --- a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/contentassist/SnippetCompletionProposal.java +++ b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/contentassist/SnippetCompletionProposal.java @@ -289,29 +289,60 @@ private static List getGenericSnippets(SnippetCompletionContext return Collections.emptyList(); } int tokenLocation = completionContext.getTokenLocation(); - JavaContextType contextType = (JavaContextType) JavaLanguageServerPlugin.getInstance().getTemplateContextRegistry().getContextType(JavaContextType.ID_STATEMENTS); - if (contextType == null) { - return Collections.emptyList(); - } + JavaContextType contextType = null; + JavaContextType contextTypeAll = (JavaContextType) JavaLanguageServerPlugin.getInstance().getTemplateContextRegistry().getContextType(JavaContextType.ID_ALL); ICompilationUnit cu = scc.getCompilationUnit(); IDocument document = new Document(cu.getSource()); - DocumentTemplateContext javaContext = contextType.createContext(document, completionContext.getOffset(), completionToken.length, cu); + DocumentTemplateContext javaContext; + DocumentTemplateContext javaContextAll; Template[] templates = null; + Template[] templatesAll = JavaLanguageServerPlugin.getInstance().getTemplateStore().getTemplates(JavaContextType.ID_ALL); if ((tokenLocation & CompletionContext.TL_STATEMENT_START) != 0) { + contextType = (JavaContextType) JavaLanguageServerPlugin.getInstance().getTemplateContextRegistry().getContextType(JavaContextType.ID_STATEMENTS); + if (contextType != null) { + javaContext = contextType.createContext(document, completionContext.getOffset(), completionToken.length, cu); + } else { + javaContext = null; + } templates = JavaLanguageServerPlugin.getInstance().getTemplateStore().getTemplates(JavaContextType.ID_STATEMENTS); + if (contextTypeAll != null) { + javaContextAll = contextTypeAll.createContext(document, completionContext.getOffset(), completionToken.length, cu); + } else { + javaContextAll = null; + } + } else if ((tokenLocation & CompletionContext.TL_MEMBER_START) != 0) { + contextType = (JavaContextType) JavaLanguageServerPlugin.getInstance().getTemplateContextRegistry().getContextType(JavaContextType.ID_MEMBERS); + if (contextType != null) { + javaContext = contextType.createContext(document, completionContext.getOffset(), completionToken.length, cu); + } else { + javaContext = null; + } + templates = JavaLanguageServerPlugin.getInstance().getTemplateStore().getTemplates(JavaContextType.ID_MEMBERS); + if (contextTypeAll != null) { + javaContextAll = contextTypeAll.createContext(document, completionContext.getOffset(), completionToken.length, cu); + } else { + javaContextAll = null; + } } else { - // We only support statement templates for now. + javaContext = null; + javaContextAll = null; } - if (templates == null || templates.length == 0) { + if ((javaContext == null && javaContextAll == null) || (templates == null && templatesAll == null) || templates.length == 0) { return Collections.emptyList(); } String uri = JDTUtils.toURI(cu); Template[] availableTemplates = Arrays.stream(templates).filter(t -> javaContext.canEvaluate(t)).toArray(Template[]::new); + Template[] availableTemplatesAll = Arrays.stream(templatesAll).filter(t -> javaContextAll.canEvaluate(t)).toArray(Template[]::new); List proposals = new ArrayList<>(); - for (int i = 0; i < availableTemplates.length; i++) { - Template template = availableTemplates[i]; + for (int i = 0; i < availableTemplates.length + availableTemplatesAll.length; i++) { + Template template; + if (i < availableTemplates.length) { + template = availableTemplates[i]; + } else { + template = availableTemplatesAll[i - availableTemplates.length]; + } final CompletionItem item = new CompletionItem(); item.setLabel(template.getName()); item.setKind(CompletionItemKind.Snippet); @@ -378,7 +409,7 @@ private static boolean isCompletionItemLabelDetailsSupport() { } public static String evaluateGenericTemplate(ICompilationUnit cu, CompletionContext completionContext, Template template) { - JavaContextType contextType = (JavaContextType) JavaLanguageServerPlugin.getInstance().getTemplateContextRegistry().getContextType(JavaContextType.ID_STATEMENTS); + JavaContextType contextType = (JavaContextType) JavaLanguageServerPlugin.getInstance().getTemplateContextRegistry().getContextType(template.getContextTypeId()); char[] completionToken = completionContext.getToken(); if (contextType == null || completionToken == null) { return null; diff --git a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/contentassist/SnippetUtils.java b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/contentassist/SnippetUtils.java index d7c9a46d0d..c8bea9cd6b 100644 --- a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/contentassist/SnippetUtils.java +++ b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/contentassist/SnippetUtils.java @@ -23,6 +23,7 @@ public class SnippetUtils { private static final String MARKDOWN_LANGUAGE = "java"; private static final String TM_SELECTED_TEXT = "\\$TM_SELECTED_TEXT"; + private static final String TM_FILENAME_BASE = "\\$TM_FILENAME_BASE"; private SnippetUtils() { } @@ -44,12 +45,14 @@ public static String templateToSnippet(String pattern) { } public static Either beautifyDocument(String raw) { + // remove the list of choices and replace with the first choice (e.g. |public,protected,private| -> public) + String escapedString = raw.replaceAll("\\$\\{\\d\\|(.*?),.*?\\}", "$1"); // remove the placeholder for the plain cursor like: ${0}, ${1:variable} - String escapedString = raw.replaceAll("\\$\\{\\d:?(.*?)\\}", "$1"); - + escapedString = escapedString.replaceAll("\\$\\{\\d:?(.*?)\\}", "$1"); // Replace the reserved variable with empty string. // See: https://github.com/eclipse/eclipse.jdt.ls/issues/1220 escapedString = escapedString.replaceAll(TM_SELECTED_TEXT, ""); + escapedString = escapedString.replaceAll(TM_FILENAME_BASE, ""); if (JavaLanguageServerPlugin.getPreferencesManager() != null && JavaLanguageServerPlugin.getPreferencesManager().getClientPreferences() != null && JavaLanguageServerPlugin.getPreferencesManager().getClientPreferences().isSupportsCompletionDocumentationMarkdown()) { diff --git a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/corext/template/java/CodeSnippetTemplate.java b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/corext/template/java/CodeSnippetTemplate.java index b0cd79ef8e..48c94f6d7b 100644 --- a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/corext/template/java/CodeSnippetTemplate.java +++ b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/corext/template/java/CodeSnippetTemplate.java @@ -29,12 +29,21 @@ public enum CodeSnippetTemplate { IFELSE(TemplatePreferences.IFELSE_ID, JavaContextType.ID_STATEMENTS, TemplatePreferences.IFELSE_CONTENT, TemplatePreferences.IFELSE_DESCRIPTION), IFNULL(TemplatePreferences.IFNULL_ID, JavaContextType.ID_STATEMENTS, TemplatePreferences.IFNULL_CONTENT, TemplatePreferences.IFNULL_DESCRIPTION), IFNOTNULL(TemplatePreferences.IFNOTNULL_ID, JavaContextType.ID_STATEMENTS, TemplatePreferences.IFNOTNULL_CONTENT, TemplatePreferences.IFNOTNULL_DESCRIPTION), - + SWITCH(TemplatePreferences.SWITCH_ID, JavaContextType.ID_STATEMENTS, TemplatePreferences.SWITCH_CONTENT, TemplatePreferences.SWITCH_DESCRIPTION), + TRY_CATCH(TemplatePreferences.TRYCATCH_ID, JavaContextType.ID_STATEMENTS, TemplatePreferences.TRYCATCH_CONTENT, TemplatePreferences.TRYCATCH_DESCRIPTION), + TRY_RESOURCES(TemplatePreferences.TRYRESOURCES_ID, JavaContextType.ID_STATEMENTS, TemplatePreferences.TRYRESOURCES_CONTENT, TemplatePreferences.TRYRESOURCES_DESCRIPTION), + CTOR(TemplatePreferences.CTOR_ID, JavaContextType.ID_MEMBERS, TemplatePreferences.CTOR_CONTENT, TemplatePreferences.CTOR_DESCRIPTION), + METHOD(TemplatePreferences.METHOD_ID, JavaContextType.ID_MEMBERS, TemplatePreferences.METHOD_CONTENT, TemplatePreferences.METHOD_DESCRIPTION), + FIELD(TemplatePreferences.FIELD_ID, JavaContextType.ID_MEMBERS, TemplatePreferences.FIELD_CONTENT, TemplatePreferences.FIELD_DESCRIPTION), + MAIN(TemplatePreferences.MAIN_ID, JavaContextType.ID_MEMBERS, TemplatePreferences.MAIN_CONTENT, TemplatePreferences.MAIN_DESCRIPTION), + NEW(TemplatePreferences.NEW_ID, JavaContextType.ID_ALL, TemplatePreferences.NEW_CONTENT, TemplatePreferences.NEW_DESCRIPTION), + // the following snippets are the same as above but with different alias, since users may not easily find them if they come from different IDEs. SOUT(TemplatePreferences.SOUT_ID, JavaContextType.ID_STATEMENTS, TemplatePreferences.SYSOUT_CONTENT, TemplatePreferences.SYSOUT_DESCRIPTION), SERR(TemplatePreferences.SERR_ID, JavaContextType.ID_STATEMENTS, TemplatePreferences.SYSERR_CONTENT, TemplatePreferences.SYSERR_DESCRIPTION), SOUTM(TemplatePreferences.SOUTM_ID, JavaContextType.ID_STATEMENTS, TemplatePreferences.SYSTRACE_CONTENT, TemplatePreferences.SYSTRACE_DESCRIPTION), - ITER(TemplatePreferences.ITER_ID, JavaContextType.ID_STATEMENTS, TemplatePreferences.FOREACH_CONTENT, TemplatePreferences.FOREACH_DESCRIPTION); + ITER(TemplatePreferences.ITER_ID, JavaContextType.ID_STATEMENTS, TemplatePreferences.FOREACH_CONTENT, TemplatePreferences.FOREACH_DESCRIPTION), + PSVM(TemplatePreferences.PSVM_ID, JavaContextType.ID_MEMBERS, TemplatePreferences.MAIN_CONTENT, TemplatePreferences.MAIN_DESCRIPTION); //@formatter:on private final String templateId; @@ -75,6 +84,15 @@ class TemplatePreferences { public static final String IFELSE_ID = "org.eclipse.jdt.ls.templates.ifelse"; public static final String IFNULL_ID = "org.eclipse.jdt.ls.templates.ifnull"; public static final String IFNOTNULL_ID = "org.eclipse.jdt.ls.templates.ifnotnull"; + public static final String SWITCH_ID = "org.eclipse.jdt.ls.templates.switch"; + public static final String TRYCATCH_ID = "org.eclipse.jdt.ls.templates.trycatch"; + public static final String TRYRESOURCES_ID = "org.eclipse.jdt.ls.templates.tryresources"; + public static final String MAIN_ID = "org.eclipse.jdt.ls.templates.main"; + public static final String PSVM_ID = "org.eclipse.jdt.ls.templates.psvm"; + public static final String CTOR_ID = "org.eclipse.jdt.ls.templates.ctor"; + public static final String METHOD_ID = "org.eclipse.jdt.ls.templates.method"; + public static final String NEW_ID = "org.eclipse.jdt.ls.templates.new"; + public static final String FIELD_ID = "org.eclipse.jdt.ls.templates.field"; // DefaultContents public static final String SYSOUT_CONTENT = "System.out.println($${0});"; @@ -88,6 +106,14 @@ class TemplatePreferences { public static final String IFELSE_CONTENT = "if ($${1:${condition:var(boolean)}}) {\n" + "\t$${2}\n" + "} else {\n" + "\t$${0}\n" + "}"; public static final String IFNULL_CONTENT = "if ($${1:${name:var}} == null) {\n" + "\t$$TM_SELECTED_TEXT$${0}\n" + "}"; public static final String IFNOTNULL_CONTENT = "if ($${1:${name:var}} != null) {\n" + "\t$$TM_SELECTED_TEXT$${0}\n" + "}"; + public static final String SWITCH_CONTENT = "switch ($${1:${key:var}}) {\n" + "\tcase $${2:value}:\n" + "\t\t$${0}\n" + "\t\tbreak;\n\n" + "\tdefault:\n" + "\t\tbreak;\n" + "}"; + public static final String TRYCATCH_CONTENT = "try {\n" + "\t$$TM_SELECTED_TEXT$${1}\n" + "} catch ($${2:Exception} $${3:e}) {\n" + "\t$${0}// TODO: handle exception\n" + "}"; + public static final String TRYRESOURCES_CONTENT = "try ($${1}) {\n" + "\t$$TM_SELECTED_TEXT$${2}\n" + "} catch ($${3:Exception} $${4:e}) {\n" + "\t$${0}// TODO: handle exception\n" + "}"; + public static final String MAIN_CONTENT = "public static void main(String[] args) {\n" + "\t$${0}\n" + "}"; + public static final String CTOR_CONTENT = "$${1|public,protected,private|} $${2:$$TM_FILENAME_BASE}($${3}) {\n" + "\t$${4:super();}$${0}\n" + "}"; + public static final String METHOD_CONTENT = "$${1|public,protected,private|}$${2| , static |}$${3:void} $${4:name}($${5}) {\n" + "\t$${0}\n" + "}"; + public static final String NEW_CONTENT = "$${1:Object} $${2:foo} = new $${1}($${3});\n" + "$${0}"; + public static final String FIELD_CONTENT = "$${1|public,protected,private|} $${2:String} $${3:name};"; // Descriptions public static final String SYSOUT_DESCRIPTION = "print to standard out"; @@ -101,4 +127,12 @@ class TemplatePreferences { public static final String IFELSE_DESCRIPTION = "if-else statement"; public static final String IFNULL_DESCRIPTION = "if statement checking for null"; public static final String IFNOTNULL_DESCRIPTION = "if statement checking for not null"; + public static final String SWITCH_DESCRIPTION = "switch statement"; + public static final String TRYCATCH_DESCRIPTION = "try/catch block"; + public static final String TRYRESOURCES_DESCRIPTION = "try/catch block with resources"; + public static final String MAIN_DESCRIPTION = "public static main method"; + public static final String CTOR_DESCRIPTION = "constructor"; + public static final String METHOD_DESCRIPTION = "method"; + public static final String NEW_DESCRIPTION = "create new object"; + public static final String FIELD_DESCRIPTION = "field"; } diff --git a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/corext/template/java/JavaContextType.java b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/corext/template/java/JavaContextType.java index 5534619440..3fee9acf04 100644 --- a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/corext/template/java/JavaContextType.java +++ b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/corext/template/java/JavaContextType.java @@ -50,9 +50,9 @@ public class JavaContextType extends AbstractJavaContextTypeCore { */ public static final String ID_MODULE = "module"; //$NON-NLS-1$ - public JavaContextType() { - setId(ID_STATEMENTS); - setName(ID_STATEMENTS); + public JavaContextType(String contextType) { + setId(contextType); + setName(contextType); } @Override diff --git a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/corext/template/java/JavaContextTypeRegistry.java b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/corext/template/java/JavaContextTypeRegistry.java index 903529d049..b9b1f26bcc 100644 --- a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/corext/template/java/JavaContextTypeRegistry.java +++ b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/corext/template/java/JavaContextTypeRegistry.java @@ -25,9 +25,17 @@ public class JavaContextTypeRegistry extends ContextTypeRegistry { public JavaContextTypeRegistry() { - JavaContextType contextType = new JavaContextType(); - contextType.initializeContextTypeResolvers(); - addContextType(contextType); + JavaContextType contextTypeStatements = new JavaContextType(JavaContextType.ID_STATEMENTS); + contextTypeStatements.initializeContextTypeResolvers(); + addContextType(contextTypeStatements); + + JavaContextType contextTypeMembers = new JavaContextType(JavaContextType.ID_MEMBERS); + contextTypeMembers.initializeContextTypeResolvers(); + addContextType(contextTypeMembers); + + JavaContextType contextTypeAll = new JavaContextType(JavaContextType.ID_ALL); + contextTypeAll.initializeContextTypeResolvers(); + addContextType(contextTypeAll); JavaPostfixContextType postfixContextType = new JavaPostfixContextType(); postfixContextType.initializeContextTypeResolvers(); diff --git a/org.eclipse.jdt.ls.tests/src/org/eclipse/jdt/ls/core/internal/handlers/CompletionHandlerTest.java b/org.eclipse.jdt.ls.tests/src/org/eclipse/jdt/ls/core/internal/handlers/CompletionHandlerTest.java index 40ab039022..7de1b5fddd 100644 --- a/org.eclipse.jdt.ls.tests/src/org/eclipse/jdt/ls/core/internal/handlers/CompletionHandlerTest.java +++ b/org.eclipse.jdt.ls.tests/src/org/eclipse/jdt/ls/core/internal/handlers/CompletionHandlerTest.java @@ -1348,7 +1348,7 @@ public void testSnippet_nested_inner_interface() throws JavaModelException { CompletionList list = requestCompletions(unit, "package org.sample;\npublic interface Test {}\npublic interface InnerTest{\n"); assertNotNull(list); - List items = new ArrayList<>(list.getItems()); + List items = list.getItems().stream().filter(item -> item.getSortText() != null).collect(Collectors.toCollection(ArrayList::new)); assertFalse(items.isEmpty()); items.sort((i1, i2) -> (i1.getSortText().compareTo(i2.getSortText()))); @@ -1473,7 +1473,7 @@ public void testSnippet_nested_inner_class() throws JavaModelException { CompletionList list = requestCompletions(unit, "package org.sample;\npublic class Test {}\npublic class InnerTest{\n"); assertNotNull(list); - List items = new ArrayList<>(list.getItems()); + List items = list.getItems().stream().filter(item -> item.getSortText() != null).collect(Collectors.toCollection(ArrayList::new)); assertFalse(items.isEmpty()); items.sort((i1, i2) -> (i1.getSortText().compareTo(i2.getSortText())));