From 3ef70918a9f38669d6601d66dca55ecb38b2c0a3 Mon Sep 17 00:00:00 2001 From: tferr Date: Thu, 3 Dec 2020 21:35:22 -0500 Subject: [PATCH 01/33] Jython: Add auto-completion support for non-static methods! --- .../autocompletion/JythonAutoCompletion.java | 24 ++++- .../JythonAutocompletionProvider.java | 99 ++++++++++++++----- 2 files changed, 96 insertions(+), 27 deletions(-) diff --git a/src/main/java/org/scijava/ui/swing/script/autocompletion/JythonAutoCompletion.java b/src/main/java/org/scijava/ui/swing/script/autocompletion/JythonAutoCompletion.java index 340e626b..88fa286e 100644 --- a/src/main/java/org/scijava/ui/swing/script/autocompletion/JythonAutoCompletion.java +++ b/src/main/java/org/scijava/ui/swing/script/autocompletion/JythonAutoCompletion.java @@ -47,7 +47,8 @@ public JythonAutoCompletion(final CompletionProvider provider) { } static private final Pattern importPattern = Pattern.compile("^(from[ \\t]+([a-zA-Z_][a-zA-Z0-9._]*)[ \\t]+|)import[ \\t]+([a-zA-Z_][a-zA-Z0-9_]*[ \\ta-zA-Z0-9_,]*)[ \\t]*([\\\\]*|)[ \\t]*(#.*|)$"), - tripleQuotePattern = Pattern.compile("\"\"\""); + tripleQuotePattern = Pattern.compile("\"\"\""), + variableDeclarationPattern = Pattern.compile("([a-zA-Z_][a-zA-Z0-9._]*)\\s*=\\s*([A-Z_][a-zA-Z0-9._]*)(?:\\()"); // E.g., in 'imp=ImagePlus()' grou1: imp; group2: ImagePlus static public class Import { final public String className, @@ -65,7 +66,26 @@ public Import(final String packageName, final String[] parts, final int lineNumb this(packageName + "." + parts[0], 3 == parts.length ? parts[2] : null, lineNumber); } } - + + static public final String findClassAliasOfVariable(final String variable, String inputText) { + final String[] lines = inputText.split("\n"); + for (int i = 0; i < lines.length; ++i) { + final String line = lines[i]; + final Matcher matcher = variableDeclarationPattern.matcher(line); + if (matcher.find()) { + // a line containing a variable declaration +// System.out.println("Queried variable: " + variable); +// System.out.println("Hit: line #" + i + ": " + line); +// System.out.println("Matcher g1: " + matcher.group(1)); +// System.out.println("Matcher g2: " + matcher.group(2)); + if (variable.equals(matcher.group(1))) { + return matcher.group(2); + } + } + } + return null; + } + static public final HashMap findImportedClasses(final String text) { final HashMap importedClasses = new HashMap<>(); String packageName = ""; diff --git a/src/main/java/org/scijava/ui/swing/script/autocompletion/JythonAutocompletionProvider.java b/src/main/java/org/scijava/ui/swing/script/autocompletion/JythonAutocompletionProvider.java index 5f8c2e1d..ca1b5284 100644 --- a/src/main/java/org/scijava/ui/swing/script/autocompletion/JythonAutocompletionProvider.java +++ b/src/main/java/org/scijava/ui/swing/script/autocompletion/JythonAutocompletionProvider.java @@ -42,6 +42,7 @@ import java.util.stream.Collectors; import java.util.stream.Stream; +import javax.swing.text.BadLocationException; import javax.swing.text.JTextComponent; import org.fife.ui.autocomplete.BasicCompletion; @@ -135,13 +136,13 @@ public List getCompletions(final String text) { final Matcher m1f = fastImport.matcher(text); if (m1f.find()) return asCompletionList(ClassUtil.findClassNamesForPackage(m1f.group(2)).map(formatter::singleToImportStatement), ""); - + // E.g. "from ij.gui import Roi, Po" to expand to PolygonRoi, PointRoi for Jython // or e.g. "importClass(Package.ij" to expand to a fully qualified class name for Javascript final Matcher m2 = importStatement.matcher(text); if (m2.find()) { - String packageName = m2.group(3), - className = m2.group(4); // incomplete or empty, or multiple separated by commas with the last one incomplete or empty + final String packageName = m2.group(3); // incomplete or empty, or multiple separated by commas with the last one incomplete or empty + String className = m2.group(4); System.out.println("m2 matches className: " + className); final String[] bycomma = className.split(","); @@ -174,31 +175,79 @@ public List getCompletions(final String text) { } final Matcher m4 = staticMethodOrField.matcher(text); - if (m4.find()) { - try { - final String simpleClassName = m4.group(3), // expected complete, e.g. ImagePlus - methodOrFieldSeed = m4.group(4).toLowerCase(); // incomplete: e.g. "GR", a string to search for in the class declared fields or methods - - // Scan the script, parse the imports, find first one matching - final Import im = JythonAutoCompletion.findImportedClasses(text_area.getText()).get(simpleClassName); - if (null != im) { - final Class c = Class.forName(im.className); - final ArrayList matches = new ArrayList<>(); - for (final Field f: c.getFields()) { - if (Modifier.isStatic(f.getModifiers()) && f.getName().toLowerCase().startsWith(methodOrFieldSeed)) - matches.add(f.getName()); - } - for (final Method m: c.getMethods()) { - if (Modifier.isStatic(m.getModifiers()) && m.getName().toLowerCase().startsWith(methodOrFieldSeed)) - matches.add(m.getName() + "("); - } - return asCompletionList(matches.stream(), m4.group(1)); + try { + + String simpleClassName; + String methodOrFieldSeed; + String pre; + boolean isStatic; + + if (m4.find()) { + + // a call to a static class + pre = m4.group(1); + simpleClassName = m4.group(3); // expected complete, e.g. ImagePlus + methodOrFieldSeed = m4.group(4).toLowerCase(); // incomplete: e.g. "GR", a string to search for in the class declared fields or methods + isStatic = true; + + } else { + + // a call to an instantiated class + final String[] varAndSeed = getVariableAnSeedAtCaretLocation(); + if (varAndSeed == null) return Collections.emptyList();; + + simpleClassName = JythonAutoCompletion.findClassAliasOfVariable(varAndSeed[0], text_area.getText()); + if (simpleClassName == null) return Collections.emptyList(); + + pre = varAndSeed[0] + "."; + methodOrFieldSeed = varAndSeed[1]; + isStatic = false; + +// System.out.println("simpleClassName: " + simpleClassName); +// System.out.println("methodOrFieldSeed: " + methodOrFieldSeed); + + } + + // Retrieve all methods and fields, if the seed is empty + final boolean includeAll = methodOrFieldSeed.trim().isEmpty(); + + // Scan the script, parse the imports, find first one matching + final Import im = JythonAutoCompletion.findImportedClasses(text_area.getText()).get(simpleClassName); + if (null != im) { + final Class c = Class.forName(im.className); + final ArrayList matches = new ArrayList<>(); + for (final Field f: c.getFields()) { + if (isStatic == Modifier.isStatic(f.getModifiers()) && + (includeAll || f.getName().toLowerCase().startsWith(methodOrFieldSeed))) + matches.add(f.getName()); } - } catch (Exception e) { - e.printStackTrace(); + for (final Method m: c.getMethods()) { + if (isStatic == Modifier.isStatic(m.getModifiers()) && + (includeAll || m.getName().toLowerCase().startsWith(methodOrFieldSeed))) + matches.add(m.getName() + "("); + } + return asCompletionList(matches.stream(), pre); } + } catch (final Exception e) { + e.printStackTrace(); } - + return Collections.emptyList(); } + + private String[] getVariableAnSeedAtCaretLocation() { + try { + final int caretOffset = text_area.getCaretPosition(); + final int lineNumber = text_area.getLineOfOffset(caretOffset); + final int startOffset = text_area.getLineStartOffset(lineNumber); + final String lineUpToCaret = text_area.getText(startOffset, caretOffset - startOffset); + final String[] words = lineUpToCaret.split("\\s+"); + final String[] varAndSeed = words[words.length - 1].split("\\."); + return (varAndSeed.length == 2) ? varAndSeed : new String[] { varAndSeed[varAndSeed.length - 1], "" }; + } catch (final BadLocationException e) { + e.printStackTrace(); + } + return null; + } + } From f7899f649fe30b263e6f6b884fa0fecd8537d83c Mon Sep 17 00:00:00 2001 From: tferr Date: Thu, 3 Dec 2020 22:03:34 -0500 Subject: [PATCH 02/33] Warn user on invalid imports Currently, if there is e.g., a typo in an import declaration completion will fail with a ClassNotFoundException. This commit adds placeholders to the completion list to warn user about such cases, --- .../JythonAutocompletionProvider.java | 36 ++++++++++++------- 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/src/main/java/org/scijava/ui/swing/script/autocompletion/JythonAutocompletionProvider.java b/src/main/java/org/scijava/ui/swing/script/autocompletion/JythonAutocompletionProvider.java index ca1b5284..de8b0e3e 100644 --- a/src/main/java/org/scijava/ui/swing/script/autocompletion/JythonAutocompletionProvider.java +++ b/src/main/java/org/scijava/ui/swing/script/autocompletion/JythonAutocompletionProvider.java @@ -214,19 +214,23 @@ public List getCompletions(final String text) { // Scan the script, parse the imports, find first one matching final Import im = JythonAutoCompletion.findImportedClasses(text_area.getText()).get(simpleClassName); if (null != im) { - final Class c = Class.forName(im.className); - final ArrayList matches = new ArrayList<>(); - for (final Field f: c.getFields()) { - if (isStatic == Modifier.isStatic(f.getModifiers()) && - (includeAll || f.getName().toLowerCase().startsWith(methodOrFieldSeed))) - matches.add(f.getName()); + try { + final Class c = Class.forName(im.className); + final ArrayList matches = new ArrayList<>(); + for (final Field f: c.getFields()) { + if (isStatic == Modifier.isStatic(f.getModifiers()) && + (includeAll || f.getName().toLowerCase().startsWith(methodOrFieldSeed))) + matches.add(f.getName()); + } + for (final Method m: c.getMethods()) { + if (isStatic == Modifier.isStatic(m.getModifiers()) && + (includeAll || m.getName().toLowerCase().startsWith(methodOrFieldSeed))) + matches.add(m.getName() + "("); + } + return asCompletionList(matches.stream(), pre); + } catch (final ClassNotFoundException ignored) { + return classUnavailableCompletions(simpleClassName + "."); } - for (final Method m: c.getMethods()) { - if (isStatic == Modifier.isStatic(m.getModifiers()) && - (includeAll || m.getName().toLowerCase().startsWith(methodOrFieldSeed))) - matches.add(m.getName() + "("); - } - return asCompletionList(matches.stream(), pre); } } catch (final Exception e) { e.printStackTrace(); @@ -235,6 +239,14 @@ public List getCompletions(final String text) { return Collections.emptyList(); } + private List classUnavailableCompletions(final String pre) { + // placeholder completions to warn users class was not available + final List list = new ArrayList<>(); + list.add(new BasicCompletion(JythonAutocompletionProvider.this, pre + "CLASS_NOT_FOUND")); + list.add(new BasicCompletion(JythonAutocompletionProvider.this, pre + "INVALID_IMPORT")); + return list; + } + private String[] getVariableAnSeedAtCaretLocation() { try { final int caretOffset = text_area.getCaretPosition(); From 422f160f75c1d724c9af23812db907a5f7902ed1 Mon Sep 17 00:00:00 2001 From: tferr Date: Fri, 4 Dec 2020 04:36:57 -0500 Subject: [PATCH 03/33] Add parameters and definitions to the description of completions --- .../JythonAutocompletionProvider.java | 53 ++++++++++++++++--- 1 file changed, 45 insertions(+), 8 deletions(-) diff --git a/src/main/java/org/scijava/ui/swing/script/autocompletion/JythonAutocompletionProvider.java b/src/main/java/org/scijava/ui/swing/script/autocompletion/JythonAutocompletionProvider.java index de8b0e3e..ab133b60 100644 --- a/src/main/java/org/scijava/ui/swing/script/autocompletion/JythonAutocompletionProvider.java +++ b/src/main/java/org/scijava/ui/swing/script/autocompletion/JythonAutocompletionProvider.java @@ -31,12 +31,12 @@ import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Modifier; +import java.lang.reflect.Parameter; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Vector; -import java.util.function.Function; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; @@ -216,18 +216,18 @@ public List getCompletions(final String text) { if (null != im) { try { final Class c = Class.forName(im.className); - final ArrayList matches = new ArrayList<>(); + final ArrayList completions = new ArrayList<>(); for (final Field f: c.getFields()) { if (isStatic == Modifier.isStatic(f.getModifiers()) && (includeAll || f.getName().toLowerCase().startsWith(methodOrFieldSeed))) - matches.add(f.getName()); + completions.add(getCompletion(pre, f, c)); } for (final Method m: c.getMethods()) { if (isStatic == Modifier.isStatic(m.getModifiers()) && (includeAll || m.getName().toLowerCase().startsWith(methodOrFieldSeed))) - matches.add(m.getName() + "("); + completions.add(getCompletion(pre+m.getName(), m, c)); } - return asCompletionList(matches.stream(), pre); + return completions; } catch (final ClassNotFoundException ignored) { return classUnavailableCompletions(simpleClassName + "."); } @@ -239,11 +239,48 @@ public List getCompletions(final String text) { return Collections.emptyList(); } + private Completion getCompletion(final String pre, final Field field, final Class c) { + // TODO: Add hyperlinks for javadocs + final StringBuffer summary = new StringBuffer(); + summary.append("").append(field.getName()).append(""); + summary.append("
"); + summary.append("
Defined in:"); + summary.append("
").append(c.getName()); + summary.append("
"); + return new BasicCompletion(JythonAutocompletionProvider.this, pre+field.getName(), null, summary.toString()); + } + + private Completion getCompletion(final String replacementText, final Method method, final Class c) { + // TODO: Add hyperlinks for javadocs + final StringBuffer summary = new StringBuffer(); + { + summary.append("").append(method.getName()).append("("); + final Parameter[] params = method.getParameters(); + if (params.length > 0) { + for (final Parameter parameter : params) { + summary.append(parameter.getType().getSimpleName()).append(", "); + } + summary.setLength(summary.length() - 2); // remove trailing ', '; + } + summary.append(")"); + summary.append("
"); + summary.append("
Returns:"); + summary.append("
").append(method.getReturnType().getSimpleName()); + summary.append("
Defined in:"); + summary.append("
").append(c.getName()); + summary.append("
"); + } + return new BasicCompletion(JythonAutocompletionProvider.this, replacementText, null, summary.toString()); + } + private List classUnavailableCompletions(final String pre) { - // placeholder completions to warn users class was not available + // placeholder completions to warn users class was not available (repeated to force pop-up display) final List list = new ArrayList<>(); - list.add(new BasicCompletion(JythonAutocompletionProvider.this, pre + "CLASS_NOT_FOUND")); - list.add(new BasicCompletion(JythonAutocompletionProvider.this, pre + "INVALID_IMPORT")); + final String summary = "Class not found or invalid import. See " + + String.format("SciJavaDocs", ClassUtil.scijava_javadoc_URL) + + " or search for help"; + list.add(new BasicCompletion(JythonAutocompletionProvider.this, pre + "?", null, summary)); + list.add(new BasicCompletion(JythonAutocompletionProvider.this, pre + "?", null, summary)); return list; } From 092a86c3c29f31b1c12458ef7dc26fd39db6992f Mon Sep 17 00:00:00 2001 From: tferr Date: Fri, 4 Dec 2020 12:27:56 -0500 Subject: [PATCH 04/33] Fix visibility of field --- .../org/scijava/ui/swing/script/autocompletion/ClassUtil.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/scijava/ui/swing/script/autocompletion/ClassUtil.java b/src/main/java/org/scijava/ui/swing/script/autocompletion/ClassUtil.java index 1cad39e8..b5643bae 100644 --- a/src/main/java/org/scijava/ui/swing/script/autocompletion/ClassUtil.java +++ b/src/main/java/org/scijava/ui/swing/script/autocompletion/ClassUtil.java @@ -48,7 +48,7 @@ public class ClassUtil { - static private final String scijava_javadoc_URL = "https://javadoc.scijava.org/"; // with ending slash + static final String scijava_javadoc_URL = "https://javadoc.scijava.org/"; // with ending slash /** Cache of class names vs list of URLs found in the pom.xml files of their contaning jar files, if any. */ static private final Map class_urls = new HashMap<>(); From 686f15b66688383d05d1a7423f63285b5864bee2 Mon Sep 17 00:00:00 2001 From: haesleinhuepf Date: Fri, 4 Dec 2020 19:32:42 +0100 Subject: [PATCH 05/33] auto-complete words that contain a term (not just those which start with it) --- .../script/autocompletion/JythonAutocompletionProvider.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/scijava/ui/swing/script/autocompletion/JythonAutocompletionProvider.java b/src/main/java/org/scijava/ui/swing/script/autocompletion/JythonAutocompletionProvider.java index ab133b60..c6b154d2 100644 --- a/src/main/java/org/scijava/ui/swing/script/autocompletion/JythonAutocompletionProvider.java +++ b/src/main/java/org/scijava/ui/swing/script/autocompletion/JythonAutocompletionProvider.java @@ -219,12 +219,12 @@ public List getCompletions(final String text) { final ArrayList completions = new ArrayList<>(); for (final Field f: c.getFields()) { if (isStatic == Modifier.isStatic(f.getModifiers()) && - (includeAll || f.getName().toLowerCase().startsWith(methodOrFieldSeed))) + (includeAll || f.getName().toLowerCase().contains(methodOrFieldSeed))) completions.add(getCompletion(pre, f, c)); } for (final Method m: c.getMethods()) { if (isStatic == Modifier.isStatic(m.getModifiers()) && - (includeAll || m.getName().toLowerCase().startsWith(methodOrFieldSeed))) + (includeAll || m.getName().toLowerCase().contains(methodOrFieldSeed))) completions.add(getCompletion(pre+m.getName(), m, c)); } return completions; From b7d520133d5ff411758ffdb0df06a30b295da75d Mon Sep 17 00:00:00 2001 From: haesleinhuepf Date: Fri, 4 Dec 2020 20:29:17 +0100 Subject: [PATCH 06/33] added compatibility for import-as statements --- .../script/autocompletion/JythonAutoCompletion.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/main/java/org/scijava/ui/swing/script/autocompletion/JythonAutoCompletion.java b/src/main/java/org/scijava/ui/swing/script/autocompletion/JythonAutoCompletion.java index 88fa286e..167cf691 100644 --- a/src/main/java/org/scijava/ui/swing/script/autocompletion/JythonAutoCompletion.java +++ b/src/main/java/org/scijava/ui/swing/script/autocompletion/JythonAutoCompletion.java @@ -47,6 +47,7 @@ public JythonAutoCompletion(final CompletionProvider provider) { } static private final Pattern importPattern = Pattern.compile("^(from[ \\t]+([a-zA-Z_][a-zA-Z0-9._]*)[ \\t]+|)import[ \\t]+([a-zA-Z_][a-zA-Z0-9_]*[ \\ta-zA-Z0-9_,]*)[ \\t]*([\\\\]*|)[ \\t]*(#.*|)$"), + importAsPattern = Pattern.compile("^(from[ \\t]+([a-zA-Z_][a-zA-Z0-9._]*)[ \\t]+|)import[ \\t]+([a-zA-Z_][a-zA-Z0-9_]*[ \\ta-zA-Z0-9_,]*)[ \\t]*([\\\\]*|)[ \\t]*(#.*|)as[ \\t]+([a-zA-Z_][a-zA-Z0-9_]*[ \\ta-zA-Z0-9_,]*)[ \\t]*([\\\\]*|)[ \\t]*(#.*|)$"), tripleQuotePattern = Pattern.compile("\"\"\""), variableDeclarationPattern = Pattern.compile("([a-zA-Z_][a-zA-Z0-9._]*)\\s*=\\s*([A-Z_][a-zA-Z0-9._]*)(?:\\()"); // E.g., in 'imp=ImagePlus()' grou1: imp; group2: ImagePlus @@ -138,6 +139,15 @@ static public final HashMap findImportedClasses(final String tex } endingBackslash = null != m.group(4) && m.group(4).length() > 0 && '\\' == m.group(4).charAt(0); } + final Matcher m1 = importAsPattern.matcher(line); + if (m1.find()) { + packageName = null == m1.group(2) ? "" : m1.group(2); + for (final String simpleClassName : m1.group(3).split(",")) { + final Import im = new Import(packageName, simpleClassName.trim().split("\\s"), i); + importedClasses.put(m1.group(4), im); + } + endingBackslash = null != m1.group(5) && m1.group(5).length() > 0 && '\\' == m1.group(5).charAt(0); + } } return importedClasses; From 77c3664de9abdb8252425fd1b2c62863dd99141b Mon Sep 17 00:00:00 2001 From: haesleinhuepf Date: Fri, 4 Dec 2020 20:29:43 +0100 Subject: [PATCH 07/33] allow classes and aliases with lower case first character --- .../script/autocompletion/JythonAutocompletionProvider.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/scijava/ui/swing/script/autocompletion/JythonAutocompletionProvider.java b/src/main/java/org/scijava/ui/swing/script/autocompletion/JythonAutocompletionProvider.java index ab133b60..46d6aa35 100644 --- a/src/main/java/org/scijava/ui/swing/script/autocompletion/JythonAutocompletionProvider.java +++ b/src/main/java/org/scijava/ui/swing/script/autocompletion/JythonAutocompletionProvider.java @@ -88,8 +88,8 @@ public boolean isValidChar(final char c) { fromImport = Pattern.compile("^((from|import)[ \\t]+)([a-zA-Z_][a-zA-Z0-9._]*)$"), fastImport = Pattern.compile("^(from[ \\t]+)([a-zA-Z_][a-zA-Z0-9._]*)[ \\t]+$"), importStatement = Pattern.compile("^((from[ \\t]+([a-zA-Z0-9._]+)[ \\t]+|[ \\t]*)import[ \\t]+)([a-zA-Z0-9_., \\t]*)$"), - simpleClassName = Pattern.compile("^(.*[ \\t]+|)([A-Z_][a-zA-Z0-9_]+)$"), - staticMethodOrField = Pattern.compile("^((.*[ \\t]+|)([A-Z_][a-zA-Z0-9_]*)\\.)([a-zA-Z0-9_]*)$"); + simpleClassName = Pattern.compile("^(.*[ \\t]+|)([a-zA-Z0-9_]+)$"), + staticMethodOrField = Pattern.compile("^((.*[ \\t]+|)([a-zA-Z0-9_]*)\\.)([a-zA-Z0-9_]*)$"); private final List asCompletionList(final Stream stream, final String pre) { return stream From 121ad9cd12a8a1fa749cd565bc5e938962e1033e Mon Sep 17 00:00:00 2001 From: tferr Date: Fri, 4 Dec 2020 15:34:04 -0500 Subject: [PATCH 08/33] Suggestion list: Ensure entries starting with seed remain on top of list --- .../JythonAutocompletionProvider.java | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/main/java/org/scijava/ui/swing/script/autocompletion/JythonAutocompletionProvider.java b/src/main/java/org/scijava/ui/swing/script/autocompletion/JythonAutocompletionProvider.java index c6b154d2..23568e0d 100644 --- a/src/main/java/org/scijava/ui/swing/script/autocompletion/JythonAutocompletionProvider.java +++ b/src/main/java/org/scijava/ui/swing/script/autocompletion/JythonAutocompletionProvider.java @@ -34,6 +34,7 @@ import java.lang.reflect.Parameter; import java.util.ArrayList; import java.util.Collections; +import java.util.Comparator; import java.util.List; import java.util.Vector; @@ -227,6 +228,25 @@ public List getCompletions(final String text) { (includeAll || m.getName().toLowerCase().contains(methodOrFieldSeed))) completions.add(getCompletion(pre+m.getName(), m, c)); } + + Collections.sort(completions, new Comparator() { + int prefix1Index = Integer.MAX_VALUE; + int prefix2Index = Integer.MAX_VALUE; + @Override + public int compare(final Completion o1, final Completion o2) { + prefix1Index = Integer.MAX_VALUE; + prefix2Index = Integer.MAX_VALUE; + if (o1.getReplacementText().startsWith(pre)) + prefix1Index = 0; + if (o2.getReplacementText().startsWith(pre)) + prefix2Index = 0; + if (prefix1Index == prefix2Index) + return o1.compareTo(o2); + else + return prefix1Index - prefix2Index; + } + }); + return completions; } catch (final ClassNotFoundException ignored) { return classUnavailableCompletions(simpleClassName + "."); From 1f3bea669ec21bb451e5bf778ebdbe71600d9323 Mon Sep 17 00:00:00 2001 From: tferr Date: Fri, 4 Dec 2020 15:37:59 -0500 Subject: [PATCH 09/33] Fix formatting issues As per: - https://github.com/scijava/script-editor/pull/51#discussion_r536304693 - https://github.com/scijava/script-editor/pull/51#discussion_r536303696 --- .../script/autocompletion/JythonAutocompletionProvider.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/scijava/ui/swing/script/autocompletion/JythonAutocompletionProvider.java b/src/main/java/org/scijava/ui/swing/script/autocompletion/JythonAutocompletionProvider.java index 23568e0d..aa2158a8 100644 --- a/src/main/java/org/scijava/ui/swing/script/autocompletion/JythonAutocompletionProvider.java +++ b/src/main/java/org/scijava/ui/swing/script/autocompletion/JythonAutocompletionProvider.java @@ -142,8 +142,8 @@ public List getCompletions(final String text) { // or e.g. "importClass(Package.ij" to expand to a fully qualified class name for Javascript final Matcher m2 = importStatement.matcher(text); if (m2.find()) { - final String packageName = m2.group(3); // incomplete or empty, or multiple separated by commas with the last one incomplete or empty - String className = m2.group(4); + final String packageName = m2.group(3); + String className = m2.group(4); // incomplete or empty, or multiple separated by commas with the last one incomplete or empty System.out.println("m2 matches className: " + className); final String[] bycomma = className.split(","); @@ -195,7 +195,7 @@ public List getCompletions(final String text) { // a call to an instantiated class final String[] varAndSeed = getVariableAnSeedAtCaretLocation(); - if (varAndSeed == null) return Collections.emptyList();; + if (varAndSeed == null) return Collections.emptyList(); simpleClassName = JythonAutoCompletion.findClassAliasOfVariable(varAndSeed[0], text_area.getText()); if (simpleClassName == null) return Collections.emptyList(); From 721fe9182a35de0a7d63db9f47e8aba01f3af710 Mon Sep 17 00:00:00 2001 From: haesleinhuepf Date: Fri, 4 Dec 2020 21:50:43 +0100 Subject: [PATCH 10/33] Revert "added compatibility for import-as statements" This reverts commit b7d520133d5ff411758ffdb0df06a30b295da75d. --- .../script/autocompletion/JythonAutoCompletion.java | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/src/main/java/org/scijava/ui/swing/script/autocompletion/JythonAutoCompletion.java b/src/main/java/org/scijava/ui/swing/script/autocompletion/JythonAutoCompletion.java index 167cf691..88fa286e 100644 --- a/src/main/java/org/scijava/ui/swing/script/autocompletion/JythonAutoCompletion.java +++ b/src/main/java/org/scijava/ui/swing/script/autocompletion/JythonAutoCompletion.java @@ -47,7 +47,6 @@ public JythonAutoCompletion(final CompletionProvider provider) { } static private final Pattern importPattern = Pattern.compile("^(from[ \\t]+([a-zA-Z_][a-zA-Z0-9._]*)[ \\t]+|)import[ \\t]+([a-zA-Z_][a-zA-Z0-9_]*[ \\ta-zA-Z0-9_,]*)[ \\t]*([\\\\]*|)[ \\t]*(#.*|)$"), - importAsPattern = Pattern.compile("^(from[ \\t]+([a-zA-Z_][a-zA-Z0-9._]*)[ \\t]+|)import[ \\t]+([a-zA-Z_][a-zA-Z0-9_]*[ \\ta-zA-Z0-9_,]*)[ \\t]*([\\\\]*|)[ \\t]*(#.*|)as[ \\t]+([a-zA-Z_][a-zA-Z0-9_]*[ \\ta-zA-Z0-9_,]*)[ \\t]*([\\\\]*|)[ \\t]*(#.*|)$"), tripleQuotePattern = Pattern.compile("\"\"\""), variableDeclarationPattern = Pattern.compile("([a-zA-Z_][a-zA-Z0-9._]*)\\s*=\\s*([A-Z_][a-zA-Z0-9._]*)(?:\\()"); // E.g., in 'imp=ImagePlus()' grou1: imp; group2: ImagePlus @@ -139,15 +138,6 @@ static public final HashMap findImportedClasses(final String tex } endingBackslash = null != m.group(4) && m.group(4).length() > 0 && '\\' == m.group(4).charAt(0); } - final Matcher m1 = importAsPattern.matcher(line); - if (m1.find()) { - packageName = null == m1.group(2) ? "" : m1.group(2); - for (final String simpleClassName : m1.group(3).split(",")) { - final Import im = new Import(packageName, simpleClassName.trim().split("\\s"), i); - importedClasses.put(m1.group(4), im); - } - endingBackslash = null != m1.group(5) && m1.group(5).length() > 0 && '\\' == m1.group(5).charAt(0); - } } return importedClasses; From f9c6a69a1bef40e182c912e372301d855e0eeba3 Mon Sep 17 00:00:00 2001 From: tferr Date: Fri, 4 Dec 2020 16:27:06 -0500 Subject: [PATCH 11/33] Restrict allowed whitespace in variable declaration As per https://github.com/scijava/script-editor/pull/51#discussion_r536302555 --- .../ui/swing/script/autocompletion/JythonAutoCompletion.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/scijava/ui/swing/script/autocompletion/JythonAutoCompletion.java b/src/main/java/org/scijava/ui/swing/script/autocompletion/JythonAutoCompletion.java index 88fa286e..b583baf2 100644 --- a/src/main/java/org/scijava/ui/swing/script/autocompletion/JythonAutoCompletion.java +++ b/src/main/java/org/scijava/ui/swing/script/autocompletion/JythonAutoCompletion.java @@ -48,7 +48,7 @@ public JythonAutoCompletion(final CompletionProvider provider) { static private final Pattern importPattern = Pattern.compile("^(from[ \\t]+([a-zA-Z_][a-zA-Z0-9._]*)[ \\t]+|)import[ \\t]+([a-zA-Z_][a-zA-Z0-9_]*[ \\ta-zA-Z0-9_,]*)[ \\t]*([\\\\]*|)[ \\t]*(#.*|)$"), tripleQuotePattern = Pattern.compile("\"\"\""), - variableDeclarationPattern = Pattern.compile("([a-zA-Z_][a-zA-Z0-9._]*)\\s*=\\s*([A-Z_][a-zA-Z0-9._]*)(?:\\()"); // E.g., in 'imp=ImagePlus()' grou1: imp; group2: ImagePlus + variableDeclarationPattern = Pattern.compile("([a-zA-Z_][a-zA-Z0-9._]*)[ \\t]*=[ \\t]*([A-Z_][a-zA-Z0-9._]*)(?:\\()"); // E.g., in 'imp=ImagePlus()' group1: imp; group2: ImagePlus static public class Import { final public String className, From daff9273ac9ba98472307272fe4bd14b95999e0b Mon Sep 17 00:00:00 2001 From: tferr Date: Fri, 4 Dec 2020 18:08:37 -0500 Subject: [PATCH 12/33] Improve autocompletions - Include parameters type in replacement text (`imp.draw(int, int, int, int)` vs `imp.draw(` - Improve summary - Display type of fields - Include javadoc link in description - Fix dup. text --- .../JythonAutocompletionProvider.java | 82 ++++++++++++++----- 1 file changed, 60 insertions(+), 22 deletions(-) diff --git a/src/main/java/org/scijava/ui/swing/script/autocompletion/JythonAutocompletionProvider.java b/src/main/java/org/scijava/ui/swing/script/autocompletion/JythonAutocompletionProvider.java index 4fbe4e57..e567adab 100644 --- a/src/main/java/org/scijava/ui/swing/script/autocompletion/JythonAutocompletionProvider.java +++ b/src/main/java/org/scijava/ui/swing/script/autocompletion/JythonAutocompletionProvider.java @@ -226,7 +226,7 @@ public List getCompletions(final String text) { for (final Method m: c.getMethods()) { if (isStatic == Modifier.isStatic(m.getModifiers()) && (includeAll || m.getName().toLowerCase().contains(methodOrFieldSeed))) - completions.add(getCompletion(pre+m.getName(), m, c)); + completions.add(getCompletion(pre, m, c)); } Collections.sort(completions, new Comparator() { @@ -259,38 +259,76 @@ public int compare(final Completion o1, final Completion o2) { return Collections.emptyList(); } + private String getJavaDocLink(final Class c) { + final String name = c.getCanonicalName(); + final String pkg = getDocPackage(name); + if (pkg == null) return name; + final String url = String.format("%s%s%s", ClassUtil.scijava_javadoc_URL, pkg, name.replace(".", "/")); + return String.format("%s", url, name); + } + + private String getDocPackage(final String classCanonicalName) { + //TODO: Do this programatically + if (classCanonicalName.startsWith("ij.")) + return "ImageJ1/"; + else if (classCanonicalName.startsWith("sc.fiji")) + return "Fiji/"; + else if (classCanonicalName.startsWith("net.imagej")) + return "ImageJ/"; + else if (classCanonicalName.startsWith("net.imglib2")) + return "ImgLib2/"; + else if (classCanonicalName.startsWith("org.scijava")) + return "SciJava/"; + else if (classCanonicalName.startsWith("loci.formats")) + return "Bio-Formats/"; + if (classCanonicalName.startsWith("java.")) + return "Java8/"; + else if (classCanonicalName.startsWith("sc.iview")) + return "SciView/"; + else if (classCanonicalName.startsWith("weka.")) + return "Weka/"; + else if (classCanonicalName.startsWith("inra.ijpb")) + return "MorphoLibJ/"; + return null; + } + private Completion getCompletion(final String pre, final Field field, final Class c) { - // TODO: Add hyperlinks for javadocs final StringBuffer summary = new StringBuffer(); - summary.append("").append(field.getName()).append(""); + summary.append("").append(field.getName()).append(""); + summary.append(" ("+ field.getType().getSimpleName()).append(")"); summary.append("
"); summary.append("
Defined in:"); - summary.append("
").append(c.getName()); + summary.append("
").append(getJavaDocLink(c)); summary.append("
"); return new BasicCompletion(JythonAutocompletionProvider.this, pre+field.getName(), null, summary.toString()); } - private Completion getCompletion(final String replacementText, final Method method, final Class c) { - // TODO: Add hyperlinks for javadocs + private Completion getCompletion(final String pre, final Method method, final Class c) { final StringBuffer summary = new StringBuffer(); - { - summary.append("").append(method.getName()).append("("); - final Parameter[] params = method.getParameters(); - if (params.length > 0) { - for (final Parameter parameter : params) { - summary.append(parameter.getType().getSimpleName()).append(", "); - } - summary.setLength(summary.length() - 2); // remove trailing ', '; + final StringBuffer replacementHeader = new StringBuffer(method.getName()); + String replacementString; + final int bIndex = replacementHeader.length(); // remember '(' position + replacementHeader.append("("); + final Parameter[] params = method.getParameters(); + if (params.length > 0) { + for (final Parameter parameter : params) { + replacementHeader.append(parameter.getType().getSimpleName()).append(", "); } - summary.append(")"); - summary.append("
"); - summary.append("
Returns:"); - summary.append("
").append(method.getReturnType().getSimpleName()); - summary.append("
Defined in:"); - summary.append("
").append(c.getName()); - summary.append("
"); + replacementHeader.setLength(replacementHeader.length() - 2); // remove trailing ', '; } - return new BasicCompletion(JythonAutocompletionProvider.this, replacementText, null, summary.toString()); + replacementHeader.append(")"); + replacementString = pre + replacementHeader.toString(); + + replacementHeader.replace(bIndex, bIndex+1, "
("); // In header, highlight only method name for extra contrast + summary.append("").append(replacementHeader); + summary.append("
"); + summary.append("
Returns:"); + summary.append("
").append(method.getReturnType().getSimpleName()); + summary.append("
Defined in:"); + summary.append("
").append(getJavaDocLink(c)); + summary.append("
"); + + return new BasicCompletion(JythonAutocompletionProvider.this, replacementString, null, summary.toString()); } private List classUnavailableCompletions(final String pre) { From e7870c0099c7846b57e39d8784ab1315678d9c9c Mon Sep 17 00:00:00 2001 From: tferr Date: Fri, 4 Dec 2020 22:51:29 -0500 Subject: [PATCH 13/33] Refactoring: move generic code to ClassUtil... ...This facilitates access to other future providers (Groovy, etc.) --- .../script/autocompletion/ClassUtil.java | 91 +++++++++++++++++++ .../JythonAutocompletionProvider.java | 89 +----------------- 2 files changed, 94 insertions(+), 86 deletions(-) diff --git a/src/main/java/org/scijava/ui/swing/script/autocompletion/ClassUtil.java b/src/main/java/org/scijava/ui/swing/script/autocompletion/ClassUtil.java index b5643bae..ea857038 100644 --- a/src/main/java/org/scijava/ui/swing/script/autocompletion/ClassUtil.java +++ b/src/main/java/org/scijava/ui/swing/script/autocompletion/ClassUtil.java @@ -30,6 +30,9 @@ import java.io.File; import java.io.IOException; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.Parameter; import java.net.URL; import java.net.URLConnection; import java.util.ArrayList; @@ -46,6 +49,10 @@ import java.util.regex.Pattern; import java.util.stream.Stream; +import org.fife.ui.autocomplete.BasicCompletion; +import org.fife.ui.autocomplete.Completion; +import org.fife.ui.autocomplete.CompletionProvider; + public class ClassUtil { static final String scijava_javadoc_URL = "https://javadoc.scijava.org/"; // with ending slash @@ -335,4 +342,88 @@ static public final ArrayList findSimpleClassNamesStartingWith(final Str } return matches; } + + private static String getJavaDocLink(final Class c) { + final String name = c.getCanonicalName(); + final String pkg = getDocPackage(name); + if (pkg == null) return name; + final String url = String.format("%s%s%s", scijava_javadoc_URL, pkg, name.replace(".", "/")); + return String.format("%s", url, name); + } + + private static String getDocPackage(final String classCanonicalName) { + //TODO: Do this programatically + if (classCanonicalName.startsWith("ij.")) + return "ImageJ1/"; + else if (classCanonicalName.startsWith("sc.fiji")) + return "Fiji/"; + else if (classCanonicalName.startsWith("net.imagej")) + return "ImageJ/"; + else if (classCanonicalName.startsWith("net.imglib2")) + return "ImgLib2/"; + else if (classCanonicalName.startsWith("org.scijava")) + return "SciJava/"; + else if (classCanonicalName.startsWith("loci.formats")) + return "Bio-Formats/"; + if (classCanonicalName.startsWith("java.")) + return "Java8/"; + else if (classCanonicalName.startsWith("sc.iview")) + return "SciView/"; + else if (classCanonicalName.startsWith("weka.")) + return "Weka/"; + else if (classCanonicalName.startsWith("inra.ijpb")) + return "MorphoLibJ/"; + return null; + } + + static Completion getCompletion(final CompletionProvider provider, final String pre, final Field field, final Class c) { + final StringBuffer summary = new StringBuffer(); + summary.append("").append(field.getName()).append(""); + summary.append(" ("+ field.getType().getSimpleName()).append(")"); + summary.append("
"); + summary.append("
Defined in:"); + summary.append("
").append(getJavaDocLink(c)); + summary.append("
"); + return new BasicCompletion(provider, pre+field.getName(), null, summary.toString()); + } + + static Completion getCompletion(final CompletionProvider provider, final String pre, final Method method, final Class c) { + final StringBuffer summary = new StringBuffer(); + final StringBuffer replacementHeader = new StringBuffer(method.getName()); + String replacementString; + final int bIndex = replacementHeader.length(); // remember '(' position + replacementHeader.append("("); + final Parameter[] params = method.getParameters(); + if (params.length > 0) { + for (final Parameter parameter : params) { + replacementHeader.append(parameter.getType().getSimpleName()).append(", "); + } + replacementHeader.setLength(replacementHeader.length() - 2); // remove trailing ', '; + } + replacementHeader.append(")"); + replacementString = pre + replacementHeader.toString(); + + replacementHeader.replace(bIndex, bIndex+1, "
("); // In header, highlight only method name for extra contrast + summary.append("").append(replacementHeader); + summary.append("
"); + summary.append("
Returns:"); + summary.append("
").append(method.getReturnType().getSimpleName()); + summary.append("
Defined in:"); + summary.append("
").append(getJavaDocLink(c)); + summary.append("
"); + + return new BasicCompletion(provider, replacementString, null, summary.toString()); + } + + static List classUnavailableCompletions(final CompletionProvider provider, final String pre) { + // placeholder completions to warn users class was not available (repeated to force pop-up display) + final List list = new ArrayList<>(); + final String summary = "Class not found or invalid import. See " + + String.format("SciJavaDocs", scijava_javadoc_URL) + + " or search for help"; + list.add(new BasicCompletion(provider, pre + "?", null, summary)); + list.add(new BasicCompletion(provider, pre + "?", null, summary)); + return list; + } + } diff --git a/src/main/java/org/scijava/ui/swing/script/autocompletion/JythonAutocompletionProvider.java b/src/main/java/org/scijava/ui/swing/script/autocompletion/JythonAutocompletionProvider.java index e567adab..8983b503 100644 --- a/src/main/java/org/scijava/ui/swing/script/autocompletion/JythonAutocompletionProvider.java +++ b/src/main/java/org/scijava/ui/swing/script/autocompletion/JythonAutocompletionProvider.java @@ -221,12 +221,12 @@ public List getCompletions(final String text) { for (final Field f: c.getFields()) { if (isStatic == Modifier.isStatic(f.getModifiers()) && (includeAll || f.getName().toLowerCase().contains(methodOrFieldSeed))) - completions.add(getCompletion(pre, f, c)); + completions.add(ClassUtil.getCompletion(this, pre, f, c)); } for (final Method m: c.getMethods()) { if (isStatic == Modifier.isStatic(m.getModifiers()) && (includeAll || m.getName().toLowerCase().contains(methodOrFieldSeed))) - completions.add(getCompletion(pre, m, c)); + completions.add(ClassUtil.getCompletion(this, pre, m, c)); } Collections.sort(completions, new Comparator() { @@ -249,7 +249,7 @@ public int compare(final Completion o1, final Completion o2) { return completions; } catch (final ClassNotFoundException ignored) { - return classUnavailableCompletions(simpleClassName + "."); + return ClassUtil.classUnavailableCompletions(this, simpleClassName + "."); } } } catch (final Exception e) { @@ -259,89 +259,6 @@ public int compare(final Completion o1, final Completion o2) { return Collections.emptyList(); } - private String getJavaDocLink(final Class c) { - final String name = c.getCanonicalName(); - final String pkg = getDocPackage(name); - if (pkg == null) return name; - final String url = String.format("%s%s%s", ClassUtil.scijava_javadoc_URL, pkg, name.replace(".", "/")); - return String.format("%s", url, name); - } - - private String getDocPackage(final String classCanonicalName) { - //TODO: Do this programatically - if (classCanonicalName.startsWith("ij.")) - return "ImageJ1/"; - else if (classCanonicalName.startsWith("sc.fiji")) - return "Fiji/"; - else if (classCanonicalName.startsWith("net.imagej")) - return "ImageJ/"; - else if (classCanonicalName.startsWith("net.imglib2")) - return "ImgLib2/"; - else if (classCanonicalName.startsWith("org.scijava")) - return "SciJava/"; - else if (classCanonicalName.startsWith("loci.formats")) - return "Bio-Formats/"; - if (classCanonicalName.startsWith("java.")) - return "Java8/"; - else if (classCanonicalName.startsWith("sc.iview")) - return "SciView/"; - else if (classCanonicalName.startsWith("weka.")) - return "Weka/"; - else if (classCanonicalName.startsWith("inra.ijpb")) - return "MorphoLibJ/"; - return null; - } - - private Completion getCompletion(final String pre, final Field field, final Class c) { - final StringBuffer summary = new StringBuffer(); - summary.append("").append(field.getName()).append(""); - summary.append(" ("+ field.getType().getSimpleName()).append(")"); - summary.append("
"); - summary.append("
Defined in:"); - summary.append("
").append(getJavaDocLink(c)); - summary.append("
"); - return new BasicCompletion(JythonAutocompletionProvider.this, pre+field.getName(), null, summary.toString()); - } - - private Completion getCompletion(final String pre, final Method method, final Class c) { - final StringBuffer summary = new StringBuffer(); - final StringBuffer replacementHeader = new StringBuffer(method.getName()); - String replacementString; - final int bIndex = replacementHeader.length(); // remember '(' position - replacementHeader.append("("); - final Parameter[] params = method.getParameters(); - if (params.length > 0) { - for (final Parameter parameter : params) { - replacementHeader.append(parameter.getType().getSimpleName()).append(", "); - } - replacementHeader.setLength(replacementHeader.length() - 2); // remove trailing ', '; - } - replacementHeader.append(")"); - replacementString = pre + replacementHeader.toString(); - - replacementHeader.replace(bIndex, bIndex+1, "
("); // In header, highlight only method name for extra contrast - summary.append("").append(replacementHeader); - summary.append("
"); - summary.append("
Returns:"); - summary.append("
").append(method.getReturnType().getSimpleName()); - summary.append("
Defined in:"); - summary.append("
").append(getJavaDocLink(c)); - summary.append("
"); - - return new BasicCompletion(JythonAutocompletionProvider.this, replacementString, null, summary.toString()); - } - - private List classUnavailableCompletions(final String pre) { - // placeholder completions to warn users class was not available (repeated to force pop-up display) - final List list = new ArrayList<>(); - final String summary = "Class not found or invalid import. See " - + String.format("SciJavaDocs", ClassUtil.scijava_javadoc_URL) - + " or search for help"; - list.add(new BasicCompletion(JythonAutocompletionProvider.this, pre + "?", null, summary)); - list.add(new BasicCompletion(JythonAutocompletionProvider.this, pre + "?", null, summary)); - return list; - } - private String[] getVariableAnSeedAtCaretLocation() { try { final int caretOffset = text_area.getCaretPosition(); From 89f5e08dd1982da3aa1edd6d2b4a4972bbe9783d Mon Sep 17 00:00:00 2001 From: tferr Date: Fri, 4 Dec 2020 23:03:54 -0500 Subject: [PATCH 14/33] Use frames when accessing javadocs (also fixes missing .html suffix in URL) --- .../script/autocompletion/ClassUtil.java | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/main/java/org/scijava/ui/swing/script/autocompletion/ClassUtil.java b/src/main/java/org/scijava/ui/swing/script/autocompletion/ClassUtil.java index ea857038..ea37cb65 100644 --- a/src/main/java/org/scijava/ui/swing/script/autocompletion/ClassUtil.java +++ b/src/main/java/org/scijava/ui/swing/script/autocompletion/ClassUtil.java @@ -347,32 +347,32 @@ private static String getJavaDocLink(final Class c) { final String name = c.getCanonicalName(); final String pkg = getDocPackage(name); if (pkg == null) return name; - final String url = String.format("%s%s%s", scijava_javadoc_URL, pkg, name.replace(".", "/")); + final String url = String.format("%s%s/index.html?%s.html", scijava_javadoc_URL, pkg, name.replace(".", "/")); return String.format("%s", url, name); } private static String getDocPackage(final String classCanonicalName) { //TODO: Do this programatically if (classCanonicalName.startsWith("ij.")) - return "ImageJ1/"; + return "ImageJ1"; else if (classCanonicalName.startsWith("sc.fiji")) - return "Fiji/"; + return "Fiji"; else if (classCanonicalName.startsWith("net.imagej")) - return "ImageJ/"; + return "ImageJ"; else if (classCanonicalName.startsWith("net.imglib2")) - return "ImgLib2/"; + return "ImgLib2"; else if (classCanonicalName.startsWith("org.scijava")) - return "SciJava/"; + return "SciJava"; else if (classCanonicalName.startsWith("loci.formats")) - return "Bio-Formats/"; + return "Bio-Formats"; if (classCanonicalName.startsWith("java.")) - return "Java8/"; + return "Java8"; else if (classCanonicalName.startsWith("sc.iview")) - return "SciView/"; + return "SciView"; else if (classCanonicalName.startsWith("weka.")) - return "Weka/"; + return "Weka"; else if (classCanonicalName.startsWith("inra.ijpb")) - return "MorphoLibJ/"; + return "MorphoLibJ"; return null; } From b7c51fc7064c484f6424505d7d5598f7d6eff8db Mon Sep 17 00:00:00 2001 From: Tiago Ferreira Date: Fri, 4 Dec 2020 16:32:42 -0500 Subject: [PATCH 15/33] Revert "Merge pull request #2 from haesleinhuepf/import-as_statment" At least for now. Somehow breaks variable completion This reverts commit 9a584dff0b36f56bf4d3b503fca1615cbbb3e645. --- .../script/autocompletion/JythonAutocompletionProvider.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/scijava/ui/swing/script/autocompletion/JythonAutocompletionProvider.java b/src/main/java/org/scijava/ui/swing/script/autocompletion/JythonAutocompletionProvider.java index 8983b503..8b7ba4c2 100644 --- a/src/main/java/org/scijava/ui/swing/script/autocompletion/JythonAutocompletionProvider.java +++ b/src/main/java/org/scijava/ui/swing/script/autocompletion/JythonAutocompletionProvider.java @@ -89,8 +89,8 @@ public boolean isValidChar(final char c) { fromImport = Pattern.compile("^((from|import)[ \\t]+)([a-zA-Z_][a-zA-Z0-9._]*)$"), fastImport = Pattern.compile("^(from[ \\t]+)([a-zA-Z_][a-zA-Z0-9._]*)[ \\t]+$"), importStatement = Pattern.compile("^((from[ \\t]+([a-zA-Z0-9._]+)[ \\t]+|[ \\t]*)import[ \\t]+)([a-zA-Z0-9_., \\t]*)$"), - simpleClassName = Pattern.compile("^(.*[ \\t]+|)([a-zA-Z0-9_]+)$"), - staticMethodOrField = Pattern.compile("^((.*[ \\t]+|)([a-zA-Z0-9_]*)\\.)([a-zA-Z0-9_]*)$"); + simpleClassName = Pattern.compile("^(.*[ \\t]+|)([A-Z_][a-zA-Z0-9_]+)$"), + staticMethodOrField = Pattern.compile("^((.*[ \\t]+|)([A-Z_][a-zA-Z0-9_]*)\\.)([a-zA-Z0-9_]*)$"); private final List asCompletionList(final Stream stream, final String pre) { return stream From 155c8aa89c1510deea4161b4b1160a93b01c00ab Mon Sep 17 00:00:00 2001 From: Tiago Ferreira Date: Fri, 4 Dec 2020 16:32:42 -0500 Subject: [PATCH 16/33] Revert "Merge pull request #2 from haesleinhuepf/import-as_statment" At least for now. Somehow breaks variable completion This reverts commit 9a584dff0b36f56bf4d3b503fca1615cbbb3e645. --- .../script/autocompletion/JythonAutocompletionProvider.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/scijava/ui/swing/script/autocompletion/JythonAutocompletionProvider.java b/src/main/java/org/scijava/ui/swing/script/autocompletion/JythonAutocompletionProvider.java index 8983b503..8b7ba4c2 100644 --- a/src/main/java/org/scijava/ui/swing/script/autocompletion/JythonAutocompletionProvider.java +++ b/src/main/java/org/scijava/ui/swing/script/autocompletion/JythonAutocompletionProvider.java @@ -89,8 +89,8 @@ public boolean isValidChar(final char c) { fromImport = Pattern.compile("^((from|import)[ \\t]+)([a-zA-Z_][a-zA-Z0-9._]*)$"), fastImport = Pattern.compile("^(from[ \\t]+)([a-zA-Z_][a-zA-Z0-9._]*)[ \\t]+$"), importStatement = Pattern.compile("^((from[ \\t]+([a-zA-Z0-9._]+)[ \\t]+|[ \\t]*)import[ \\t]+)([a-zA-Z0-9_., \\t]*)$"), - simpleClassName = Pattern.compile("^(.*[ \\t]+|)([a-zA-Z0-9_]+)$"), - staticMethodOrField = Pattern.compile("^((.*[ \\t]+|)([a-zA-Z0-9_]*)\\.)([a-zA-Z0-9_]*)$"); + simpleClassName = Pattern.compile("^(.*[ \\t]+|)([A-Z_][a-zA-Z0-9_]+)$"), + staticMethodOrField = Pattern.compile("^((.*[ \\t]+|)([A-Z_][a-zA-Z0-9_]*)\\.)([a-zA-Z0-9_]*)$"); private final List asCompletionList(final Stream stream, final String pre) { return stream From 96da72a4931eb0d0b73c94e22283272fad8bef0c Mon Sep 17 00:00:00 2001 From: frauzufall Date: Wed, 16 Dec 2020 10:56:43 +0100 Subject: [PATCH 17/33] [maven-release-plugin] prepare release script-editor-0.5.9 --- pom.xml | 4 ++-- release.properties | 19 +++++++++++++++++++ 2 files changed, 21 insertions(+), 2 deletions(-) create mode 100644 release.properties diff --git a/pom.xml b/pom.xml index 64b8fa3d..b80458f9 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ script-editor - 0.5.9-SNAPSHOT + 0.5.9 SciJava Script Editor Script Editor and Interpreter for SciJava script languages. @@ -127,7 +127,7 @@ scm:git:git://github.com/scijava/script-editor scm:git:git@github.com:scijava/script-editor - HEAD + script-editor-0.5.9 https://github.com/scijava/script-editor diff --git a/release.properties b/release.properties new file mode 100644 index 00000000..d4adcd00 --- /dev/null +++ b/release.properties @@ -0,0 +1,19 @@ +#release configuration +#Wed Dec 16 10:56:44 CET 2020 +project.scm.org.scijava\:script-editor.developerConnection=scm\:git\:git@github.com\:scijava/script-editor +scm.tagNameFormat=@{project.artifactId}-@{project.version} +scm.tag=script-editor-0.5.9 +pushChanges=false +scm.url=scm\:git\:git\://github.com/scijava/script-editor +preparationGoals=clean verify +project.scm.org.scijava\:script-editor.tag=HEAD +project.scm.org.scijava\:script-editor.url=https\://github.com/scijava/script-editor +remoteTagging=true +projectVersionPolicyId=default +scm.commentPrefix=[maven-release-plugin] +project.scm.org.scijava\:script-editor.connection=scm\:git\:git\://github.com/scijava/script-editor +project.dev.org.scijava\:script-editor=0.5.10-SNAPSHOT +exec.snapshotReleasePluginAllowed=false +exec.additionalArguments=-Dgpg.skip\=true -P deploy-to-scijava +completedPhase=end-release +project.rel.org.scijava\:script-editor=0.5.9 From 34a2e05fccd73258ff5608bdf15f671e8c00cfc9 Mon Sep 17 00:00:00 2001 From: tferr Date: Thu, 3 Dec 2020 21:35:22 -0500 Subject: [PATCH 18/33] Jython: Add auto-completion support for non-static methods! --- .../autocompletion/JythonAutoCompletion.java | 24 ++++- .../JythonAutocompletionProvider.java | 96 ++++++++++++++----- 2 files changed, 94 insertions(+), 26 deletions(-) diff --git a/src/main/java/org/scijava/ui/swing/script/autocompletion/JythonAutoCompletion.java b/src/main/java/org/scijava/ui/swing/script/autocompletion/JythonAutoCompletion.java index adfbcee4..9ce6c8d6 100644 --- a/src/main/java/org/scijava/ui/swing/script/autocompletion/JythonAutoCompletion.java +++ b/src/main/java/org/scijava/ui/swing/script/autocompletion/JythonAutoCompletion.java @@ -47,7 +47,8 @@ public JythonAutoCompletion(final CompletionProvider provider) { } static private final Pattern importPattern = Pattern.compile("^(from[ \\t]+([a-zA-Z_][a-zA-Z0-9._]*)[ \\t]+|)import[ \\t]+([a-zA-Z_][a-zA-Z0-9_]*[ \\ta-zA-Z0-9_,]*)[ \\t]*([\\\\]*|)[ \\t]*(#.*|)$"), - tripleQuotePattern = Pattern.compile("\"\"\""); + tripleQuotePattern = Pattern.compile("\"\"\""), + variableDeclarationPattern = Pattern.compile("([a-zA-Z_][a-zA-Z0-9._]*)\\s*=\\s*([A-Z_][a-zA-Z0-9._]*)(?:\\()"); // E.g., in 'imp=ImagePlus()' grou1: imp; group2: ImagePlus static public class Import { final public String className, @@ -65,7 +66,26 @@ public Import(final String packageName, final String[] parts, final int lineNumb this(packageName + "." + parts[0], 3 == parts.length ? parts[2] : null, lineNumber); } } - + + static public final String findClassAliasOfVariable(final String variable, String inputText) { + final String[] lines = inputText.split("\n"); + for (int i = 0; i < lines.length; ++i) { + final String line = lines[i]; + final Matcher matcher = variableDeclarationPattern.matcher(line); + if (matcher.find()) { + // a line containing a variable declaration +// System.out.println("Queried variable: " + variable); +// System.out.println("Hit: line #" + i + ": " + line); +// System.out.println("Matcher g1: " + matcher.group(1)); +// System.out.println("Matcher g2: " + matcher.group(2)); + if (variable.equals(matcher.group(1))) { + return matcher.group(2); + } + } + } + return null; + } + static public final HashMap findImportedClasses(final String text) { final HashMap importedClasses = new HashMap<>(); String packageName = ""; diff --git a/src/main/java/org/scijava/ui/swing/script/autocompletion/JythonAutocompletionProvider.java b/src/main/java/org/scijava/ui/swing/script/autocompletion/JythonAutocompletionProvider.java index 1be15159..febf0cad 100644 --- a/src/main/java/org/scijava/ui/swing/script/autocompletion/JythonAutocompletionProvider.java +++ b/src/main/java/org/scijava/ui/swing/script/autocompletion/JythonAutocompletionProvider.java @@ -148,12 +148,12 @@ public List getCompletions(final String text) { final Matcher m1f = fastImport.matcher(text); if (m1f.find()) return asCompletionList(ClassUtil.findClassNamesForPackage(m1f.group(2)).map(formatter::singleToImportStatement), ""); - + // E.g. "from ij.gui import Roi, Po" to expand to PolygonRoi, PointRoi for Jython final Matcher m2 = importStatement.matcher(text); if (m2.find()) { - String packageName = m2.group(3), - className = m2.group(4); // incomplete or empty, or multiple separated by commas with the last one incomplete or empty + final String packageName = m2.group(3); // incomplete or empty, or multiple separated by commas with the last one incomplete or empty + String className = m2.group(4); System.out.println("m2 matches className: " + className); final String[] bycomma = className.split(","); @@ -189,33 +189,81 @@ public List getCompletions(final String text) { /* Covered by listener from jython-completions final Matcher m4 = staticMethodOrField.matcher(text); - if (m4.find()) { - try { - final String simpleClassName = m4.group(3), // expected complete, e.g. ImagePlus - methodOrFieldSeed = m4.group(4).toLowerCase(); // incomplete: e.g. "GR", a string to search for in the class declared fields or methods - - // Scan the script, parse the imports, find first one matching - final Import im = JythonAutoCompletion.findImportedClasses(text_area.getText()).get(simpleClassName); - if (null != im) { - final Class c = Class.forName(im.className); - final ArrayList matches = new ArrayList<>(); - for (final Field f: c.getFields()) { - if (Modifier.isStatic(f.getModifiers()) && f.getName().toLowerCase().startsWith(methodOrFieldSeed)) - matches.add(f.getName()); - } - for (final Method m: c.getMethods()) { - if (Modifier.isStatic(m.getModifiers()) && m.getName().toLowerCase().startsWith(methodOrFieldSeed)) - matches.add(m.getName() + "("); - } - return asCompletionList(matches.stream(), m4.group(1)); + try { + + String simpleClassName; + String methodOrFieldSeed; + String pre; + boolean isStatic; + + if (m4.find()) { + + // a call to a static class + pre = m4.group(1); + simpleClassName = m4.group(3); // expected complete, e.g. ImagePlus + methodOrFieldSeed = m4.group(4).toLowerCase(); // incomplete: e.g. "GR", a string to search for in the class declared fields or methods + isStatic = true; + + } else { + + // a call to an instantiated class + final String[] varAndSeed = getVariableAnSeedAtCaretLocation(); + if (varAndSeed == null) return Collections.emptyList();; + + simpleClassName = JythonAutoCompletion.findClassAliasOfVariable(varAndSeed[0], text_area.getText()); + if (simpleClassName == null) return Collections.emptyList(); + + pre = varAndSeed[0] + "."; + methodOrFieldSeed = varAndSeed[1]; + isStatic = false; + +// System.out.println("simpleClassName: " + simpleClassName); +// System.out.println("methodOrFieldSeed: " + methodOrFieldSeed); + + } + + // Retrieve all methods and fields, if the seed is empty + final boolean includeAll = methodOrFieldSeed.trim().isEmpty(); + + // Scan the script, parse the imports, find first one matching + final Import im = JythonAutoCompletion.findImportedClasses(text_area.getText()).get(simpleClassName); + if (null != im) { + final Class c = Class.forName(im.className); + final ArrayList matches = new ArrayList<>(); + for (final Field f: c.getFields()) { + if (isStatic == Modifier.isStatic(f.getModifiers()) && + (includeAll || f.getName().toLowerCase().startsWith(methodOrFieldSeed))) + matches.add(f.getName()); } - } catch (Exception e) { - e.printStackTrace(); + for (final Method m: c.getMethods()) { + if (isStatic == Modifier.isStatic(m.getModifiers()) && + (includeAll || m.getName().toLowerCase().startsWith(methodOrFieldSeed))) + matches.add(m.getName() + "("); + } + return asCompletionList(matches.stream(), pre); } + } catch (final Exception e) { + e.printStackTrace(); } */ return Collections.emptyList(); } + + private String[] getVariableAnSeedAtCaretLocation() { + try { + final int caretOffset = text_area.getCaretPosition(); + final int lineNumber = text_area.getLineOfOffset(caretOffset); + final int startOffset = text_area.getLineStartOffset(lineNumber); + final String lineUpToCaret = text_area.getText(startOffset, caretOffset - startOffset); + final String[] words = lineUpToCaret.split("\\s+"); + final String[] varAndSeed = words[words.length - 1].split("\\."); + return (varAndSeed.length == 2) ? varAndSeed : new String[] { varAndSeed[varAndSeed.length - 1], "" }; + } catch (final BadLocationException e) { + e.printStackTrace(); + } + return null; + } + } From b17cd7adac35f066685488136c3d6293833f82a2 Mon Sep 17 00:00:00 2001 From: tferr Date: Thu, 3 Dec 2020 22:03:34 -0500 Subject: [PATCH 19/33] Warn user on invalid imports Currently, if there is e.g., a typo in an import declaration completion will fail with a ClassNotFoundException. This commit adds placeholders to the completion list to warn user about such cases, --- .../JythonAutocompletionProvider.java | 36 ++++++++++++------- 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/src/main/java/org/scijava/ui/swing/script/autocompletion/JythonAutocompletionProvider.java b/src/main/java/org/scijava/ui/swing/script/autocompletion/JythonAutocompletionProvider.java index febf0cad..0feb21b1 100644 --- a/src/main/java/org/scijava/ui/swing/script/autocompletion/JythonAutocompletionProvider.java +++ b/src/main/java/org/scijava/ui/swing/script/autocompletion/JythonAutocompletionProvider.java @@ -228,19 +228,23 @@ public List getCompletions(final String text) { // Scan the script, parse the imports, find first one matching final Import im = JythonAutoCompletion.findImportedClasses(text_area.getText()).get(simpleClassName); if (null != im) { - final Class c = Class.forName(im.className); - final ArrayList matches = new ArrayList<>(); - for (final Field f: c.getFields()) { - if (isStatic == Modifier.isStatic(f.getModifiers()) && - (includeAll || f.getName().toLowerCase().startsWith(methodOrFieldSeed))) - matches.add(f.getName()); + try { + final Class c = Class.forName(im.className); + final ArrayList matches = new ArrayList<>(); + for (final Field f: c.getFields()) { + if (isStatic == Modifier.isStatic(f.getModifiers()) && + (includeAll || f.getName().toLowerCase().startsWith(methodOrFieldSeed))) + matches.add(f.getName()); + } + for (final Method m: c.getMethods()) { + if (isStatic == Modifier.isStatic(m.getModifiers()) && + (includeAll || m.getName().toLowerCase().startsWith(methodOrFieldSeed))) + matches.add(m.getName() + "("); + } + return asCompletionList(matches.stream(), pre); + } catch (final ClassNotFoundException ignored) { + return classUnavailableCompletions(simpleClassName + "."); } - for (final Method m: c.getMethods()) { - if (isStatic == Modifier.isStatic(m.getModifiers()) && - (includeAll || m.getName().toLowerCase().startsWith(methodOrFieldSeed))) - matches.add(m.getName() + "("); - } - return asCompletionList(matches.stream(), pre); } } catch (final Exception e) { e.printStackTrace(); @@ -251,6 +255,14 @@ public List getCompletions(final String text) { return Collections.emptyList(); } + private List classUnavailableCompletions(final String pre) { + // placeholder completions to warn users class was not available + final List list = new ArrayList<>(); + list.add(new BasicCompletion(JythonAutocompletionProvider.this, pre + "CLASS_NOT_FOUND")); + list.add(new BasicCompletion(JythonAutocompletionProvider.this, pre + "INVALID_IMPORT")); + return list; + } + private String[] getVariableAnSeedAtCaretLocation() { try { final int caretOffset = text_area.getCaretPosition(); From 49d37b0a1d114637a3af2eb4b13f290ff79ac122 Mon Sep 17 00:00:00 2001 From: tferr Date: Fri, 4 Dec 2020 04:36:57 -0500 Subject: [PATCH 20/33] Add parameters and definitions to the description of completions --- .../JythonAutocompletionProvider.java | 51 ++++++++++++++++--- 1 file changed, 44 insertions(+), 7 deletions(-) diff --git a/src/main/java/org/scijava/ui/swing/script/autocompletion/JythonAutocompletionProvider.java b/src/main/java/org/scijava/ui/swing/script/autocompletion/JythonAutocompletionProvider.java index 0feb21b1..7121df17 100644 --- a/src/main/java/org/scijava/ui/swing/script/autocompletion/JythonAutocompletionProvider.java +++ b/src/main/java/org/scijava/ui/swing/script/autocompletion/JythonAutocompletionProvider.java @@ -230,18 +230,18 @@ public List getCompletions(final String text) { if (null != im) { try { final Class c = Class.forName(im.className); - final ArrayList matches = new ArrayList<>(); + final ArrayList completions = new ArrayList<>(); for (final Field f: c.getFields()) { if (isStatic == Modifier.isStatic(f.getModifiers()) && (includeAll || f.getName().toLowerCase().startsWith(methodOrFieldSeed))) - matches.add(f.getName()); + completions.add(getCompletion(pre, f, c)); } for (final Method m: c.getMethods()) { if (isStatic == Modifier.isStatic(m.getModifiers()) && (includeAll || m.getName().toLowerCase().startsWith(methodOrFieldSeed))) - matches.add(m.getName() + "("); + completions.add(getCompletion(pre+m.getName(), m, c)); } - return asCompletionList(matches.stream(), pre); + return completions; } catch (final ClassNotFoundException ignored) { return classUnavailableCompletions(simpleClassName + "."); } @@ -255,11 +255,48 @@ public List getCompletions(final String text) { return Collections.emptyList(); } + private Completion getCompletion(final String pre, final Field field, final Class c) { + // TODO: Add hyperlinks for javadocs + final StringBuffer summary = new StringBuffer(); + summary.append("").append(field.getName()).append(""); + summary.append("
"); + summary.append("
Defined in:"); + summary.append("
").append(c.getName()); + summary.append("
"); + return new BasicCompletion(JythonAutocompletionProvider.this, pre+field.getName(), null, summary.toString()); + } + + private Completion getCompletion(final String replacementText, final Method method, final Class c) { + // TODO: Add hyperlinks for javadocs + final StringBuffer summary = new StringBuffer(); + { + summary.append("").append(method.getName()).append("("); + final Parameter[] params = method.getParameters(); + if (params.length > 0) { + for (final Parameter parameter : params) { + summary.append(parameter.getType().getSimpleName()).append(", "); + } + summary.setLength(summary.length() - 2); // remove trailing ', '; + } + summary.append(")"); + summary.append("
"); + summary.append("
Returns:"); + summary.append("
").append(method.getReturnType().getSimpleName()); + summary.append("
Defined in:"); + summary.append("
").append(c.getName()); + summary.append("
"); + } + return new BasicCompletion(JythonAutocompletionProvider.this, replacementText, null, summary.toString()); + } + private List classUnavailableCompletions(final String pre) { - // placeholder completions to warn users class was not available + // placeholder completions to warn users class was not available (repeated to force pop-up display) final List list = new ArrayList<>(); - list.add(new BasicCompletion(JythonAutocompletionProvider.this, pre + "CLASS_NOT_FOUND")); - list.add(new BasicCompletion(JythonAutocompletionProvider.this, pre + "INVALID_IMPORT")); + final String summary = "Class not found or invalid import. See " + + String.format("SciJavaDocs", ClassUtil.scijava_javadoc_URL) + + " or search for help"; + list.add(new BasicCompletion(JythonAutocompletionProvider.this, pre + "?", null, summary)); + list.add(new BasicCompletion(JythonAutocompletionProvider.this, pre + "?", null, summary)); return list; } From ce3bb7ab1f7527984108e0471441db8825c0de79 Mon Sep 17 00:00:00 2001 From: tferr Date: Fri, 4 Dec 2020 12:27:56 -0500 Subject: [PATCH 21/33] Fix visibility of field --- .../org/scijava/ui/swing/script/autocompletion/ClassUtil.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/scijava/ui/swing/script/autocompletion/ClassUtil.java b/src/main/java/org/scijava/ui/swing/script/autocompletion/ClassUtil.java index 1cad39e8..b5643bae 100644 --- a/src/main/java/org/scijava/ui/swing/script/autocompletion/ClassUtil.java +++ b/src/main/java/org/scijava/ui/swing/script/autocompletion/ClassUtil.java @@ -48,7 +48,7 @@ public class ClassUtil { - static private final String scijava_javadoc_URL = "https://javadoc.scijava.org/"; // with ending slash + static final String scijava_javadoc_URL = "https://javadoc.scijava.org/"; // with ending slash /** Cache of class names vs list of URLs found in the pom.xml files of their contaning jar files, if any. */ static private final Map class_urls = new HashMap<>(); From 92dfdb3209ce98a9440ce486642836ce4dcf5756 Mon Sep 17 00:00:00 2001 From: haesleinhuepf Date: Fri, 4 Dec 2020 19:32:42 +0100 Subject: [PATCH 22/33] auto-complete words that contain a term (not just those which start with it) --- .../script/autocompletion/JythonAutocompletionProvider.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/scijava/ui/swing/script/autocompletion/JythonAutocompletionProvider.java b/src/main/java/org/scijava/ui/swing/script/autocompletion/JythonAutocompletionProvider.java index 7121df17..86ba1389 100644 --- a/src/main/java/org/scijava/ui/swing/script/autocompletion/JythonAutocompletionProvider.java +++ b/src/main/java/org/scijava/ui/swing/script/autocompletion/JythonAutocompletionProvider.java @@ -233,12 +233,12 @@ public List getCompletions(final String text) { final ArrayList completions = new ArrayList<>(); for (final Field f: c.getFields()) { if (isStatic == Modifier.isStatic(f.getModifiers()) && - (includeAll || f.getName().toLowerCase().startsWith(methodOrFieldSeed))) + (includeAll || f.getName().toLowerCase().contains(methodOrFieldSeed))) completions.add(getCompletion(pre, f, c)); } for (final Method m: c.getMethods()) { if (isStatic == Modifier.isStatic(m.getModifiers()) && - (includeAll || m.getName().toLowerCase().startsWith(methodOrFieldSeed))) + (includeAll || m.getName().toLowerCase().contains(methodOrFieldSeed))) completions.add(getCompletion(pre+m.getName(), m, c)); } return completions; From 1a3ffaa071a838966223a37077e84666b9d1ce1e Mon Sep 17 00:00:00 2001 From: haesleinhuepf Date: Fri, 4 Dec 2020 20:29:17 +0100 Subject: [PATCH 23/33] added compatibility for import-as statements --- .../script/autocompletion/JythonAutoCompletion.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/main/java/org/scijava/ui/swing/script/autocompletion/JythonAutoCompletion.java b/src/main/java/org/scijava/ui/swing/script/autocompletion/JythonAutoCompletion.java index 9ce6c8d6..d3db6bf2 100644 --- a/src/main/java/org/scijava/ui/swing/script/autocompletion/JythonAutoCompletion.java +++ b/src/main/java/org/scijava/ui/swing/script/autocompletion/JythonAutoCompletion.java @@ -47,6 +47,7 @@ public JythonAutoCompletion(final CompletionProvider provider) { } static private final Pattern importPattern = Pattern.compile("^(from[ \\t]+([a-zA-Z_][a-zA-Z0-9._]*)[ \\t]+|)import[ \\t]+([a-zA-Z_][a-zA-Z0-9_]*[ \\ta-zA-Z0-9_,]*)[ \\t]*([\\\\]*|)[ \\t]*(#.*|)$"), + importAsPattern = Pattern.compile("^(from[ \\t]+([a-zA-Z_][a-zA-Z0-9._]*)[ \\t]+|)import[ \\t]+([a-zA-Z_][a-zA-Z0-9_]*[ \\ta-zA-Z0-9_,]*)[ \\t]*([\\\\]*|)[ \\t]*(#.*|)as[ \\t]+([a-zA-Z_][a-zA-Z0-9_]*[ \\ta-zA-Z0-9_,]*)[ \\t]*([\\\\]*|)[ \\t]*(#.*|)$"), tripleQuotePattern = Pattern.compile("\"\"\""), variableDeclarationPattern = Pattern.compile("([a-zA-Z_][a-zA-Z0-9._]*)\\s*=\\s*([A-Z_][a-zA-Z0-9._]*)(?:\\()"); // E.g., in 'imp=ImagePlus()' grou1: imp; group2: ImagePlus @@ -138,6 +139,15 @@ static public final HashMap findImportedClasses(final String tex } endingBackslash = null != m.group(4) && m.group(4).length() > 0 && '\\' == m.group(4).charAt(0); } + final Matcher m1 = importAsPattern.matcher(line); + if (m1.find()) { + packageName = null == m1.group(2) ? "" : m1.group(2); + for (final String simpleClassName : m1.group(3).split(",")) { + final Import im = new Import(packageName, simpleClassName.trim().split("\\s"), i); + importedClasses.put(m1.group(4), im); + } + endingBackslash = null != m1.group(5) && m1.group(5).length() > 0 && '\\' == m1.group(5).charAt(0); + } } return importedClasses; From 8d155fa64ee9a6dd3721d27a9c3fe01f79f4ec67 Mon Sep 17 00:00:00 2001 From: haesleinhuepf Date: Fri, 4 Dec 2020 20:29:43 +0100 Subject: [PATCH 24/33] allow classes and aliases with lower case first character --- .../script/autocompletion/JythonAutocompletionProvider.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/scijava/ui/swing/script/autocompletion/JythonAutocompletionProvider.java b/src/main/java/org/scijava/ui/swing/script/autocompletion/JythonAutocompletionProvider.java index 86ba1389..0e00e5bd 100644 --- a/src/main/java/org/scijava/ui/swing/script/autocompletion/JythonAutocompletionProvider.java +++ b/src/main/java/org/scijava/ui/swing/script/autocompletion/JythonAutocompletionProvider.java @@ -86,8 +86,8 @@ public boolean isValidChar(final char c) { fromImport = Pattern.compile("^((from|import)[ \\t]+)([a-zA-Z_][a-zA-Z0-9._]*)$"), fastImport = Pattern.compile("^(from[ \\t]+)([a-zA-Z_][a-zA-Z0-9._]*)[ \\t]+$"), importStatement = Pattern.compile("^((from[ \\t]+([a-zA-Z0-9._]+)[ \\t]+|[ \\t]*)import[ \\t]+)([a-zA-Z0-9_., \\t]*)$"), - simpleClassName = Pattern.compile("^(.*[ \\t]+|)([A-Z_][a-zA-Z0-9_]+)$"), - staticMethodOrField = Pattern.compile("^((.*[ \\t]+|)([A-Z_][a-zA-Z0-9_]*)\\.)([a-zA-Z0-9_]*)$"); + simpleClassName = Pattern.compile("^(.*[ \\t]+|)([a-zA-Z0-9_]+)$"), + staticMethodOrField = Pattern.compile("^((.*[ \\t]+|)([a-zA-Z0-9_]*)\\.)([a-zA-Z0-9_]*)$"); private final List asCompletionList(final Stream stream, final String pre) { return stream From 8b4bf539c335bf3aa4cf3cdd85c4d754fcb59dca Mon Sep 17 00:00:00 2001 From: tferr Date: Fri, 4 Dec 2020 15:34:04 -0500 Subject: [PATCH 25/33] Suggestion list: Ensure entries starting with seed remain on top of list --- .../JythonAutocompletionProvider.java | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/main/java/org/scijava/ui/swing/script/autocompletion/JythonAutocompletionProvider.java b/src/main/java/org/scijava/ui/swing/script/autocompletion/JythonAutocompletionProvider.java index 0e00e5bd..206f8eb5 100644 --- a/src/main/java/org/scijava/ui/swing/script/autocompletion/JythonAutocompletionProvider.java +++ b/src/main/java/org/scijava/ui/swing/script/autocompletion/JythonAutocompletionProvider.java @@ -30,6 +30,7 @@ import java.util.ArrayList; import java.util.Collections; +import java.util.Comparator; import java.util.List; import java.util.Vector; import java.util.regex.Matcher; @@ -241,6 +242,25 @@ public List getCompletions(final String text) { (includeAll || m.getName().toLowerCase().contains(methodOrFieldSeed))) completions.add(getCompletion(pre+m.getName(), m, c)); } + + Collections.sort(completions, new Comparator() { + int prefix1Index = Integer.MAX_VALUE; + int prefix2Index = Integer.MAX_VALUE; + @Override + public int compare(final Completion o1, final Completion o2) { + prefix1Index = Integer.MAX_VALUE; + prefix2Index = Integer.MAX_VALUE; + if (o1.getReplacementText().startsWith(pre)) + prefix1Index = 0; + if (o2.getReplacementText().startsWith(pre)) + prefix2Index = 0; + if (prefix1Index == prefix2Index) + return o1.compareTo(o2); + else + return prefix1Index - prefix2Index; + } + }); + return completions; } catch (final ClassNotFoundException ignored) { return classUnavailableCompletions(simpleClassName + "."); From 05ee489c33edae2c4d6acb2a30cb41adda1b18d0 Mon Sep 17 00:00:00 2001 From: tferr Date: Fri, 4 Dec 2020 15:37:59 -0500 Subject: [PATCH 26/33] Fix formatting issues As per: - https://github.com/scijava/script-editor/pull/51#discussion_r536304693 - https://github.com/scijava/script-editor/pull/51#discussion_r536303696 --- .../script/autocompletion/JythonAutocompletionProvider.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/scijava/ui/swing/script/autocompletion/JythonAutocompletionProvider.java b/src/main/java/org/scijava/ui/swing/script/autocompletion/JythonAutocompletionProvider.java index 206f8eb5..76a0ddd9 100644 --- a/src/main/java/org/scijava/ui/swing/script/autocompletion/JythonAutocompletionProvider.java +++ b/src/main/java/org/scijava/ui/swing/script/autocompletion/JythonAutocompletionProvider.java @@ -153,8 +153,8 @@ public List getCompletions(final String text) { // E.g. "from ij.gui import Roi, Po" to expand to PolygonRoi, PointRoi for Jython final Matcher m2 = importStatement.matcher(text); if (m2.find()) { - final String packageName = m2.group(3); // incomplete or empty, or multiple separated by commas with the last one incomplete or empty - String className = m2.group(4); + final String packageName = m2.group(3); + String className = m2.group(4); // incomplete or empty, or multiple separated by commas with the last one incomplete or empty System.out.println("m2 matches className: " + className); final String[] bycomma = className.split(","); @@ -209,7 +209,7 @@ public List getCompletions(final String text) { // a call to an instantiated class final String[] varAndSeed = getVariableAnSeedAtCaretLocation(); - if (varAndSeed == null) return Collections.emptyList();; + if (varAndSeed == null) return Collections.emptyList(); simpleClassName = JythonAutoCompletion.findClassAliasOfVariable(varAndSeed[0], text_area.getText()); if (simpleClassName == null) return Collections.emptyList(); From 0dfa760218a1f99ceeed122de4645988cc603bc7 Mon Sep 17 00:00:00 2001 From: haesleinhuepf Date: Fri, 4 Dec 2020 21:50:43 +0100 Subject: [PATCH 27/33] Revert "added compatibility for import-as statements" This reverts commit b7d520133d5ff411758ffdb0df06a30b295da75d. --- .../script/autocompletion/JythonAutoCompletion.java | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/src/main/java/org/scijava/ui/swing/script/autocompletion/JythonAutoCompletion.java b/src/main/java/org/scijava/ui/swing/script/autocompletion/JythonAutoCompletion.java index d3db6bf2..9ce6c8d6 100644 --- a/src/main/java/org/scijava/ui/swing/script/autocompletion/JythonAutoCompletion.java +++ b/src/main/java/org/scijava/ui/swing/script/autocompletion/JythonAutoCompletion.java @@ -47,7 +47,6 @@ public JythonAutoCompletion(final CompletionProvider provider) { } static private final Pattern importPattern = Pattern.compile("^(from[ \\t]+([a-zA-Z_][a-zA-Z0-9._]*)[ \\t]+|)import[ \\t]+([a-zA-Z_][a-zA-Z0-9_]*[ \\ta-zA-Z0-9_,]*)[ \\t]*([\\\\]*|)[ \\t]*(#.*|)$"), - importAsPattern = Pattern.compile("^(from[ \\t]+([a-zA-Z_][a-zA-Z0-9._]*)[ \\t]+|)import[ \\t]+([a-zA-Z_][a-zA-Z0-9_]*[ \\ta-zA-Z0-9_,]*)[ \\t]*([\\\\]*|)[ \\t]*(#.*|)as[ \\t]+([a-zA-Z_][a-zA-Z0-9_]*[ \\ta-zA-Z0-9_,]*)[ \\t]*([\\\\]*|)[ \\t]*(#.*|)$"), tripleQuotePattern = Pattern.compile("\"\"\""), variableDeclarationPattern = Pattern.compile("([a-zA-Z_][a-zA-Z0-9._]*)\\s*=\\s*([A-Z_][a-zA-Z0-9._]*)(?:\\()"); // E.g., in 'imp=ImagePlus()' grou1: imp; group2: ImagePlus @@ -139,15 +138,6 @@ static public final HashMap findImportedClasses(final String tex } endingBackslash = null != m.group(4) && m.group(4).length() > 0 && '\\' == m.group(4).charAt(0); } - final Matcher m1 = importAsPattern.matcher(line); - if (m1.find()) { - packageName = null == m1.group(2) ? "" : m1.group(2); - for (final String simpleClassName : m1.group(3).split(",")) { - final Import im = new Import(packageName, simpleClassName.trim().split("\\s"), i); - importedClasses.put(m1.group(4), im); - } - endingBackslash = null != m1.group(5) && m1.group(5).length() > 0 && '\\' == m1.group(5).charAt(0); - } } return importedClasses; From 1d1f255c43f77ae2161851b196105d4415a60682 Mon Sep 17 00:00:00 2001 From: tferr Date: Fri, 4 Dec 2020 16:27:06 -0500 Subject: [PATCH 28/33] Restrict allowed whitespace in variable declaration As per https://github.com/scijava/script-editor/pull/51#discussion_r536302555 --- .../ui/swing/script/autocompletion/JythonAutoCompletion.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/scijava/ui/swing/script/autocompletion/JythonAutoCompletion.java b/src/main/java/org/scijava/ui/swing/script/autocompletion/JythonAutoCompletion.java index 9ce6c8d6..e5abf56d 100644 --- a/src/main/java/org/scijava/ui/swing/script/autocompletion/JythonAutoCompletion.java +++ b/src/main/java/org/scijava/ui/swing/script/autocompletion/JythonAutoCompletion.java @@ -48,7 +48,7 @@ public JythonAutoCompletion(final CompletionProvider provider) { static private final Pattern importPattern = Pattern.compile("^(from[ \\t]+([a-zA-Z_][a-zA-Z0-9._]*)[ \\t]+|)import[ \\t]+([a-zA-Z_][a-zA-Z0-9_]*[ \\ta-zA-Z0-9_,]*)[ \\t]*([\\\\]*|)[ \\t]*(#.*|)$"), tripleQuotePattern = Pattern.compile("\"\"\""), - variableDeclarationPattern = Pattern.compile("([a-zA-Z_][a-zA-Z0-9._]*)\\s*=\\s*([A-Z_][a-zA-Z0-9._]*)(?:\\()"); // E.g., in 'imp=ImagePlus()' grou1: imp; group2: ImagePlus + variableDeclarationPattern = Pattern.compile("([a-zA-Z_][a-zA-Z0-9._]*)[ \\t]*=[ \\t]*([A-Z_][a-zA-Z0-9._]*)(?:\\()"); // E.g., in 'imp=ImagePlus()' group1: imp; group2: ImagePlus static public class Import { final public String className, From 1e46e56a4dac720831f6bc5fc2eb1f82e634ba11 Mon Sep 17 00:00:00 2001 From: tferr Date: Fri, 4 Dec 2020 18:08:37 -0500 Subject: [PATCH 29/33] Improve autocompletions - Include parameters type in replacement text (`imp.draw(int, int, int, int)` vs `imp.draw(` - Improve summary - Display type of fields - Include javadoc link in description - Fix dup. text --- .../JythonAutocompletionProvider.java | 82 ++++++++++++++----- 1 file changed, 60 insertions(+), 22 deletions(-) diff --git a/src/main/java/org/scijava/ui/swing/script/autocompletion/JythonAutocompletionProvider.java b/src/main/java/org/scijava/ui/swing/script/autocompletion/JythonAutocompletionProvider.java index 76a0ddd9..6bb6a633 100644 --- a/src/main/java/org/scijava/ui/swing/script/autocompletion/JythonAutocompletionProvider.java +++ b/src/main/java/org/scijava/ui/swing/script/autocompletion/JythonAutocompletionProvider.java @@ -240,7 +240,7 @@ public List getCompletions(final String text) { for (final Method m: c.getMethods()) { if (isStatic == Modifier.isStatic(m.getModifiers()) && (includeAll || m.getName().toLowerCase().contains(methodOrFieldSeed))) - completions.add(getCompletion(pre+m.getName(), m, c)); + completions.add(getCompletion(pre, m, c)); } Collections.sort(completions, new Comparator() { @@ -275,38 +275,76 @@ public int compare(final Completion o1, final Completion o2) { return Collections.emptyList(); } + private String getJavaDocLink(final Class c) { + final String name = c.getCanonicalName(); + final String pkg = getDocPackage(name); + if (pkg == null) return name; + final String url = String.format("%s%s%s", ClassUtil.scijava_javadoc_URL, pkg, name.replace(".", "/")); + return String.format("%s", url, name); + } + + private String getDocPackage(final String classCanonicalName) { + //TODO: Do this programatically + if (classCanonicalName.startsWith("ij.")) + return "ImageJ1/"; + else if (classCanonicalName.startsWith("sc.fiji")) + return "Fiji/"; + else if (classCanonicalName.startsWith("net.imagej")) + return "ImageJ/"; + else if (classCanonicalName.startsWith("net.imglib2")) + return "ImgLib2/"; + else if (classCanonicalName.startsWith("org.scijava")) + return "SciJava/"; + else if (classCanonicalName.startsWith("loci.formats")) + return "Bio-Formats/"; + if (classCanonicalName.startsWith("java.")) + return "Java8/"; + else if (classCanonicalName.startsWith("sc.iview")) + return "SciView/"; + else if (classCanonicalName.startsWith("weka.")) + return "Weka/"; + else if (classCanonicalName.startsWith("inra.ijpb")) + return "MorphoLibJ/"; + return null; + } + private Completion getCompletion(final String pre, final Field field, final Class c) { - // TODO: Add hyperlinks for javadocs final StringBuffer summary = new StringBuffer(); - summary.append("").append(field.getName()).append(""); + summary.append("").append(field.getName()).append(""); + summary.append(" ("+ field.getType().getSimpleName()).append(")"); summary.append("
"); summary.append("
Defined in:"); - summary.append("
").append(c.getName()); + summary.append("
").append(getJavaDocLink(c)); summary.append("
"); return new BasicCompletion(JythonAutocompletionProvider.this, pre+field.getName(), null, summary.toString()); } - private Completion getCompletion(final String replacementText, final Method method, final Class c) { - // TODO: Add hyperlinks for javadocs + private Completion getCompletion(final String pre, final Method method, final Class c) { final StringBuffer summary = new StringBuffer(); - { - summary.append("").append(method.getName()).append("("); - final Parameter[] params = method.getParameters(); - if (params.length > 0) { - for (final Parameter parameter : params) { - summary.append(parameter.getType().getSimpleName()).append(", "); - } - summary.setLength(summary.length() - 2); // remove trailing ', '; + final StringBuffer replacementHeader = new StringBuffer(method.getName()); + String replacementString; + final int bIndex = replacementHeader.length(); // remember '(' position + replacementHeader.append("("); + final Parameter[] params = method.getParameters(); + if (params.length > 0) { + for (final Parameter parameter : params) { + replacementHeader.append(parameter.getType().getSimpleName()).append(", "); } - summary.append(")"); - summary.append("
"); - summary.append("
Returns:"); - summary.append("
").append(method.getReturnType().getSimpleName()); - summary.append("
Defined in:"); - summary.append("
").append(c.getName()); - summary.append("
"); + replacementHeader.setLength(replacementHeader.length() - 2); // remove trailing ', '; } - return new BasicCompletion(JythonAutocompletionProvider.this, replacementText, null, summary.toString()); + replacementHeader.append(")"); + replacementString = pre + replacementHeader.toString(); + + replacementHeader.replace(bIndex, bIndex+1, "
("); // In header, highlight only method name for extra contrast + summary.append("").append(replacementHeader); + summary.append("
"); + summary.append("
Returns:"); + summary.append("
").append(method.getReturnType().getSimpleName()); + summary.append("
Defined in:"); + summary.append("
").append(getJavaDocLink(c)); + summary.append("
"); + + return new BasicCompletion(JythonAutocompletionProvider.this, replacementString, null, summary.toString()); } private List classUnavailableCompletions(final String pre) { From ae945c0887f17b9bf2152490ebcdf2badb1f988d Mon Sep 17 00:00:00 2001 From: tferr Date: Fri, 4 Dec 2020 22:51:29 -0500 Subject: [PATCH 30/33] Refactoring: move generic code to ClassUtil... ...This facilitates access to other future providers (Groovy, etc.) --- .../script/autocompletion/ClassUtil.java | 91 +++++++++++++++++++ .../JythonAutocompletionProvider.java | 89 +----------------- 2 files changed, 94 insertions(+), 86 deletions(-) diff --git a/src/main/java/org/scijava/ui/swing/script/autocompletion/ClassUtil.java b/src/main/java/org/scijava/ui/swing/script/autocompletion/ClassUtil.java index b5643bae..ea857038 100644 --- a/src/main/java/org/scijava/ui/swing/script/autocompletion/ClassUtil.java +++ b/src/main/java/org/scijava/ui/swing/script/autocompletion/ClassUtil.java @@ -30,6 +30,9 @@ import java.io.File; import java.io.IOException; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.Parameter; import java.net.URL; import java.net.URLConnection; import java.util.ArrayList; @@ -46,6 +49,10 @@ import java.util.regex.Pattern; import java.util.stream.Stream; +import org.fife.ui.autocomplete.BasicCompletion; +import org.fife.ui.autocomplete.Completion; +import org.fife.ui.autocomplete.CompletionProvider; + public class ClassUtil { static final String scijava_javadoc_URL = "https://javadoc.scijava.org/"; // with ending slash @@ -335,4 +342,88 @@ static public final ArrayList findSimpleClassNamesStartingWith(final Str } return matches; } + + private static String getJavaDocLink(final Class c) { + final String name = c.getCanonicalName(); + final String pkg = getDocPackage(name); + if (pkg == null) return name; + final String url = String.format("%s%s%s", scijava_javadoc_URL, pkg, name.replace(".", "/")); + return String.format("%s", url, name); + } + + private static String getDocPackage(final String classCanonicalName) { + //TODO: Do this programatically + if (classCanonicalName.startsWith("ij.")) + return "ImageJ1/"; + else if (classCanonicalName.startsWith("sc.fiji")) + return "Fiji/"; + else if (classCanonicalName.startsWith("net.imagej")) + return "ImageJ/"; + else if (classCanonicalName.startsWith("net.imglib2")) + return "ImgLib2/"; + else if (classCanonicalName.startsWith("org.scijava")) + return "SciJava/"; + else if (classCanonicalName.startsWith("loci.formats")) + return "Bio-Formats/"; + if (classCanonicalName.startsWith("java.")) + return "Java8/"; + else if (classCanonicalName.startsWith("sc.iview")) + return "SciView/"; + else if (classCanonicalName.startsWith("weka.")) + return "Weka/"; + else if (classCanonicalName.startsWith("inra.ijpb")) + return "MorphoLibJ/"; + return null; + } + + static Completion getCompletion(final CompletionProvider provider, final String pre, final Field field, final Class c) { + final StringBuffer summary = new StringBuffer(); + summary.append("").append(field.getName()).append(""); + summary.append(" ("+ field.getType().getSimpleName()).append(")"); + summary.append("
"); + summary.append("
Defined in:"); + summary.append("
").append(getJavaDocLink(c)); + summary.append("
"); + return new BasicCompletion(provider, pre+field.getName(), null, summary.toString()); + } + + static Completion getCompletion(final CompletionProvider provider, final String pre, final Method method, final Class c) { + final StringBuffer summary = new StringBuffer(); + final StringBuffer replacementHeader = new StringBuffer(method.getName()); + String replacementString; + final int bIndex = replacementHeader.length(); // remember '(' position + replacementHeader.append("("); + final Parameter[] params = method.getParameters(); + if (params.length > 0) { + for (final Parameter parameter : params) { + replacementHeader.append(parameter.getType().getSimpleName()).append(", "); + } + replacementHeader.setLength(replacementHeader.length() - 2); // remove trailing ', '; + } + replacementHeader.append(")"); + replacementString = pre + replacementHeader.toString(); + + replacementHeader.replace(bIndex, bIndex+1, "
("); // In header, highlight only method name for extra contrast + summary.append("").append(replacementHeader); + summary.append("
"); + summary.append("
Returns:"); + summary.append("
").append(method.getReturnType().getSimpleName()); + summary.append("
Defined in:"); + summary.append("
").append(getJavaDocLink(c)); + summary.append("
"); + + return new BasicCompletion(provider, replacementString, null, summary.toString()); + } + + static List classUnavailableCompletions(final CompletionProvider provider, final String pre) { + // placeholder completions to warn users class was not available (repeated to force pop-up display) + final List list = new ArrayList<>(); + final String summary = "Class not found or invalid import. See " + + String.format("SciJavaDocs", scijava_javadoc_URL) + + " or search for help"; + list.add(new BasicCompletion(provider, pre + "?", null, summary)); + list.add(new BasicCompletion(provider, pre + "?", null, summary)); + return list; + } + } diff --git a/src/main/java/org/scijava/ui/swing/script/autocompletion/JythonAutocompletionProvider.java b/src/main/java/org/scijava/ui/swing/script/autocompletion/JythonAutocompletionProvider.java index 6bb6a633..296142d5 100644 --- a/src/main/java/org/scijava/ui/swing/script/autocompletion/JythonAutocompletionProvider.java +++ b/src/main/java/org/scijava/ui/swing/script/autocompletion/JythonAutocompletionProvider.java @@ -235,12 +235,12 @@ public List getCompletions(final String text) { for (final Field f: c.getFields()) { if (isStatic == Modifier.isStatic(f.getModifiers()) && (includeAll || f.getName().toLowerCase().contains(methodOrFieldSeed))) - completions.add(getCompletion(pre, f, c)); + completions.add(ClassUtil.getCompletion(this, pre, f, c)); } for (final Method m: c.getMethods()) { if (isStatic == Modifier.isStatic(m.getModifiers()) && (includeAll || m.getName().toLowerCase().contains(methodOrFieldSeed))) - completions.add(getCompletion(pre, m, c)); + completions.add(ClassUtil.getCompletion(this, pre, m, c)); } Collections.sort(completions, new Comparator() { @@ -263,7 +263,7 @@ public int compare(final Completion o1, final Completion o2) { return completions; } catch (final ClassNotFoundException ignored) { - return classUnavailableCompletions(simpleClassName + "."); + return ClassUtil.classUnavailableCompletions(this, simpleClassName + "."); } } } catch (final Exception e) { @@ -275,89 +275,6 @@ public int compare(final Completion o1, final Completion o2) { return Collections.emptyList(); } - private String getJavaDocLink(final Class c) { - final String name = c.getCanonicalName(); - final String pkg = getDocPackage(name); - if (pkg == null) return name; - final String url = String.format("%s%s%s", ClassUtil.scijava_javadoc_URL, pkg, name.replace(".", "/")); - return String.format("%s", url, name); - } - - private String getDocPackage(final String classCanonicalName) { - //TODO: Do this programatically - if (classCanonicalName.startsWith("ij.")) - return "ImageJ1/"; - else if (classCanonicalName.startsWith("sc.fiji")) - return "Fiji/"; - else if (classCanonicalName.startsWith("net.imagej")) - return "ImageJ/"; - else if (classCanonicalName.startsWith("net.imglib2")) - return "ImgLib2/"; - else if (classCanonicalName.startsWith("org.scijava")) - return "SciJava/"; - else if (classCanonicalName.startsWith("loci.formats")) - return "Bio-Formats/"; - if (classCanonicalName.startsWith("java.")) - return "Java8/"; - else if (classCanonicalName.startsWith("sc.iview")) - return "SciView/"; - else if (classCanonicalName.startsWith("weka.")) - return "Weka/"; - else if (classCanonicalName.startsWith("inra.ijpb")) - return "MorphoLibJ/"; - return null; - } - - private Completion getCompletion(final String pre, final Field field, final Class c) { - final StringBuffer summary = new StringBuffer(); - summary.append("").append(field.getName()).append(""); - summary.append(" ("+ field.getType().getSimpleName()).append(")"); - summary.append("
"); - summary.append("
Defined in:"); - summary.append("
").append(getJavaDocLink(c)); - summary.append("
"); - return new BasicCompletion(JythonAutocompletionProvider.this, pre+field.getName(), null, summary.toString()); - } - - private Completion getCompletion(final String pre, final Method method, final Class c) { - final StringBuffer summary = new StringBuffer(); - final StringBuffer replacementHeader = new StringBuffer(method.getName()); - String replacementString; - final int bIndex = replacementHeader.length(); // remember '(' position - replacementHeader.append("("); - final Parameter[] params = method.getParameters(); - if (params.length > 0) { - for (final Parameter parameter : params) { - replacementHeader.append(parameter.getType().getSimpleName()).append(", "); - } - replacementHeader.setLength(replacementHeader.length() - 2); // remove trailing ', '; - } - replacementHeader.append(")"); - replacementString = pre + replacementHeader.toString(); - - replacementHeader.replace(bIndex, bIndex+1, "
("); // In header, highlight only method name for extra contrast - summary.append("").append(replacementHeader); - summary.append("
"); - summary.append("
Returns:"); - summary.append("
").append(method.getReturnType().getSimpleName()); - summary.append("
Defined in:"); - summary.append("
").append(getJavaDocLink(c)); - summary.append("
"); - - return new BasicCompletion(JythonAutocompletionProvider.this, replacementString, null, summary.toString()); - } - - private List classUnavailableCompletions(final String pre) { - // placeholder completions to warn users class was not available (repeated to force pop-up display) - final List list = new ArrayList<>(); - final String summary = "Class not found or invalid import. See " - + String.format("SciJavaDocs", ClassUtil.scijava_javadoc_URL) - + " or search for help"; - list.add(new BasicCompletion(JythonAutocompletionProvider.this, pre + "?", null, summary)); - list.add(new BasicCompletion(JythonAutocompletionProvider.this, pre + "?", null, summary)); - return list; - } - private String[] getVariableAnSeedAtCaretLocation() { try { final int caretOffset = text_area.getCaretPosition(); From 3751e7cad045f030858a5bedcae27df2788b9ee2 Mon Sep 17 00:00:00 2001 From: tferr Date: Fri, 4 Dec 2020 23:03:54 -0500 Subject: [PATCH 31/33] Use frames when accessing javadocs (also fixes missing .html suffix in URL) --- .../script/autocompletion/ClassUtil.java | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/main/java/org/scijava/ui/swing/script/autocompletion/ClassUtil.java b/src/main/java/org/scijava/ui/swing/script/autocompletion/ClassUtil.java index ea857038..ea37cb65 100644 --- a/src/main/java/org/scijava/ui/swing/script/autocompletion/ClassUtil.java +++ b/src/main/java/org/scijava/ui/swing/script/autocompletion/ClassUtil.java @@ -347,32 +347,32 @@ private static String getJavaDocLink(final Class c) { final String name = c.getCanonicalName(); final String pkg = getDocPackage(name); if (pkg == null) return name; - final String url = String.format("%s%s%s", scijava_javadoc_URL, pkg, name.replace(".", "/")); + final String url = String.format("%s%s/index.html?%s.html", scijava_javadoc_URL, pkg, name.replace(".", "/")); return String.format("%s", url, name); } private static String getDocPackage(final String classCanonicalName) { //TODO: Do this programatically if (classCanonicalName.startsWith("ij.")) - return "ImageJ1/"; + return "ImageJ1"; else if (classCanonicalName.startsWith("sc.fiji")) - return "Fiji/"; + return "Fiji"; else if (classCanonicalName.startsWith("net.imagej")) - return "ImageJ/"; + return "ImageJ"; else if (classCanonicalName.startsWith("net.imglib2")) - return "ImgLib2/"; + return "ImgLib2"; else if (classCanonicalName.startsWith("org.scijava")) - return "SciJava/"; + return "SciJava"; else if (classCanonicalName.startsWith("loci.formats")) - return "Bio-Formats/"; + return "Bio-Formats"; if (classCanonicalName.startsWith("java.")) - return "Java8/"; + return "Java8"; else if (classCanonicalName.startsWith("sc.iview")) - return "SciView/"; + return "SciView"; else if (classCanonicalName.startsWith("weka.")) - return "Weka/"; + return "Weka"; else if (classCanonicalName.startsWith("inra.ijpb")) - return "MorphoLibJ/"; + return "MorphoLibJ"; return null; } From 2ce9f8882cba1586d72e2db74f8b10ca759cbc32 Mon Sep 17 00:00:00 2001 From: Tiago Ferreira Date: Fri, 4 Dec 2020 16:32:42 -0500 Subject: [PATCH 32/33] Revert "Merge pull request #2 from haesleinhuepf/import-as_statment" At least for now. Somehow breaks variable completion This reverts commit 9a584dff0b36f56bf4d3b503fca1615cbbb3e645. --- .../script/autocompletion/JythonAutocompletionProvider.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/scijava/ui/swing/script/autocompletion/JythonAutocompletionProvider.java b/src/main/java/org/scijava/ui/swing/script/autocompletion/JythonAutocompletionProvider.java index 296142d5..93aaf1c6 100644 --- a/src/main/java/org/scijava/ui/swing/script/autocompletion/JythonAutocompletionProvider.java +++ b/src/main/java/org/scijava/ui/swing/script/autocompletion/JythonAutocompletionProvider.java @@ -87,8 +87,8 @@ public boolean isValidChar(final char c) { fromImport = Pattern.compile("^((from|import)[ \\t]+)([a-zA-Z_][a-zA-Z0-9._]*)$"), fastImport = Pattern.compile("^(from[ \\t]+)([a-zA-Z_][a-zA-Z0-9._]*)[ \\t]+$"), importStatement = Pattern.compile("^((from[ \\t]+([a-zA-Z0-9._]+)[ \\t]+|[ \\t]*)import[ \\t]+)([a-zA-Z0-9_., \\t]*)$"), - simpleClassName = Pattern.compile("^(.*[ \\t]+|)([a-zA-Z0-9_]+)$"), - staticMethodOrField = Pattern.compile("^((.*[ \\t]+|)([a-zA-Z0-9_]*)\\.)([a-zA-Z0-9_]*)$"); + simpleClassName = Pattern.compile("^(.*[ \\t]+|)([A-Z_][a-zA-Z0-9_]+)$"), + staticMethodOrField = Pattern.compile("^((.*[ \\t]+|)([A-Z_][a-zA-Z0-9_]*)\\.)([a-zA-Z0-9_]*)$"); private final List asCompletionList(final Stream stream, final String pre) { return stream From cd732e25c828070b733d3144f6d119692316bb0f Mon Sep 17 00:00:00 2001 From: tferr Date: Sun, 24 Jan 2021 22:34:14 -0500 Subject: [PATCH 33/33] Implement CompletionText and modify ClassUtil for upcoming changes ... ... in https://github.com/fiji/jython-autocompletion While at it, do some code cleanup --- .../script/autocompletion/ClassUtil.java | 35 +++++++--- .../script/autocompletion/CompletionText.java | 67 +++++++++++++++++++ .../JythonAutocompletionProvider.java | 3 +- 3 files changed, 94 insertions(+), 11 deletions(-) create mode 100644 src/main/java/org/scijava/ui/swing/script/autocompletion/CompletionText.java diff --git a/src/main/java/org/scijava/ui/swing/script/autocompletion/ClassUtil.java b/src/main/java/org/scijava/ui/swing/script/autocompletion/ClassUtil.java index ea37cb65..8076bd76 100644 --- a/src/main/java/org/scijava/ui/swing/script/autocompletion/ClassUtil.java +++ b/src/main/java/org/scijava/ui/swing/script/autocompletion/ClassUtil.java @@ -376,21 +376,38 @@ else if (classCanonicalName.startsWith("inra.ijpb")) return null; } - static Completion getCompletion(final CompletionProvider provider, final String pre, final Field field, final Class c) { + /** + * Assembles an HTML-formatted auto-completion summary with functional + * hyperlinks + * + * @param field the field being documented + * @param c the class being documented. Expected to be documented at the + * Scijava API documentation portal. + * @return the completion summary + */ + protected static String getSummaryCompletion(final Field field, final Class c) { final StringBuffer summary = new StringBuffer(); summary.append("").append(field.getName()).append(""); - summary.append(" ("+ field.getType().getSimpleName()).append(")"); + summary.append(" (").append(field.getType().getName()).append(")"); summary.append("
"); summary.append("
Defined in:"); summary.append("
").append(getJavaDocLink(c)); summary.append("
"); - return new BasicCompletion(provider, pre+field.getName(), null, summary.toString()); + return summary.toString(); } - static Completion getCompletion(final CompletionProvider provider, final String pre, final Method method, final Class c) { + /** + * Assembles an HTML-formatted auto-completion summary with functional + * hyperlinks + * + * @param method the method being documented + * @param c the class being documented. Expected to be documented at the + * Scijava API documentation portal. + * @return the completion summary + */ + protected static String getSummaryCompletion(final Method method, final Class c) { final StringBuffer summary = new StringBuffer(); final StringBuffer replacementHeader = new StringBuffer(method.getName()); - String replacementString; final int bIndex = replacementHeader.length(); // remember '(' position replacementHeader.append("("); final Parameter[] params = method.getParameters(); @@ -401,9 +418,8 @@ static Completion getCompletion(final CompletionProvider provider, final String replacementHeader.setLength(replacementHeader.length() - 2); // remove trailing ', '; } replacementHeader.append(")"); - replacementString = pre + replacementHeader.toString(); - - replacementHeader.replace(bIndex, bIndex+1, "
("); // In header, highlight only method name for extra contrast + replacementHeader.replace(bIndex, bIndex + 1, "
("); // In header, highlight only method name for extra + // contrast summary.append("").append(replacementHeader); summary.append("
"); summary.append("
Returns:"); @@ -411,8 +427,7 @@ static Completion getCompletion(final CompletionProvider provider, final String summary.append("
Defined in:"); summary.append("
").append(getJavaDocLink(c)); summary.append("
"); - - return new BasicCompletion(provider, replacementString, null, summary.toString()); + return summary.toString(); } static List classUnavailableCompletions(final CompletionProvider provider, final String pre) { diff --git a/src/main/java/org/scijava/ui/swing/script/autocompletion/CompletionText.java b/src/main/java/org/scijava/ui/swing/script/autocompletion/CompletionText.java new file mode 100644 index 00000000..b88926a1 --- /dev/null +++ b/src/main/java/org/scijava/ui/swing/script/autocompletion/CompletionText.java @@ -0,0 +1,67 @@ +package org.scijava.ui.swing.script.autocompletion; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; + +import org.fife.ui.autocomplete.AbstractCompletion; +import org.fife.ui.autocomplete.BasicCompletion; +import org.fife.ui.autocomplete.CompletionProvider; + +public class CompletionText { + + private String replacementText; + private String description; + private String summary; + + public CompletionText(final String replacementText) { + this(replacementText, (String)null, (String)null); + } + + public CompletionText(final String replacementText, final String summary, final String description) { + this.replacementText = replacementText; + this.summary = summary; + this.description = description; + } + + public CompletionText(final String replacementText, final Class c, final Field f) { + this(replacementText, ClassUtil.getSummaryCompletion(f, c), null); + } + + public CompletionText(final String replacementText, final Class c, final Method m) { + this(replacementText, ClassUtil.getSummaryCompletion(m, c), null); + } + + public String getReplacementText() { + return replacementText; + } + + public String getDescription() { + return description; + } + + public String getSummary() { + return summary; + } + + public AbstractCompletion getCompletion(final CompletionProvider provider, final String replacementText) { + return new BasicCompletion(provider, replacementText, description, summary); + } + + public void setReplacementText(final String replacementText) { + this.replacementText = replacementText; + } + + public void setDescription(final String description) { + this.description = description; + } + + public void setSummary(final String summary) { + this.summary = summary; + } + + @Override + public String toString() { + return replacementText + " | " + description + " | " + summary; + } + +} diff --git a/src/main/java/org/scijava/ui/swing/script/autocompletion/JythonAutocompletionProvider.java b/src/main/java/org/scijava/ui/swing/script/autocompletion/JythonAutocompletionProvider.java index 93aaf1c6..9063084e 100644 --- a/src/main/java/org/scijava/ui/swing/script/autocompletion/JythonAutocompletionProvider.java +++ b/src/main/java/org/scijava/ui/swing/script/autocompletion/JythonAutocompletionProvider.java @@ -30,7 +30,6 @@ import java.util.ArrayList; import java.util.Collections; -import java.util.Comparator; import java.util.List; import java.util.Vector; import java.util.regex.Matcher; @@ -83,6 +82,7 @@ public boolean isValidChar(final char c) { return Character.isLetterOrDigit(c) || '.' == c || ' ' == c; } + @SuppressWarnings("unused") static private final Pattern fromImport = Pattern.compile("^((from|import)[ \\t]+)([a-zA-Z_][a-zA-Z0-9._]*)$"), fastImport = Pattern.compile("^(from[ \\t]+)([a-zA-Z_][a-zA-Z0-9._]*)[ \\t]+$"), @@ -275,6 +275,7 @@ public int compare(final Completion o1, final Completion o2) { return Collections.emptyList(); } + @SuppressWarnings("unused") private String[] getVariableAnSeedAtCaretLocation() { try { final int caretOffset = text_area.getCaretPosition();