Skip to content

Commit 4e1a665

Browse files
committed
Autocompletion for python (jython) using the LanguageSupportService.
1 parent 4316bc7 commit 4e1a665

File tree

9 files changed

+174
-23
lines changed

9 files changed

+174
-23
lines changed

src/main/java/org/scijava/ui/swing/script/EditorPane.java

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -71,8 +71,7 @@
7171
import org.scijava.script.ScriptHeaderService;
7272
import org.scijava.script.ScriptLanguage;
7373
import org.scijava.script.ScriptService;
74-
import org.scijava.ui.swing.script.autocompletion.AutoCompletionProxy;
75-
import org.scijava.ui.swing.script.autocompletion.AutocompletionProvider;
74+
import org.scijava.ui.swing.script.autocompletion.JythonAutoCompletion;
7675
import org.scijava.util.FileUtils;
7776

7877
/**
@@ -108,6 +107,8 @@ public class EditorPane extends RSyntaxTextArea implements DocumentListener {
108107
private PrefService prefService;
109108
@Parameter
110109
private LogService log;
110+
111+
private JythonAutoCompletion autoCompletionProxy;
111112

112113
/**
113114
* Constructor.
@@ -126,8 +127,6 @@ public EditorPane() {
126127
wordMovement(-1, true));
127128
ToolTipManager.sharedInstance().registerComponent(this);
128129
getDocument().addDocumentListener(this);
129-
130-
new AutoCompletionProxy(new AutocompletionProvider(this)).install(this);
131130
}
132131

133132
@Override

src/main/java/org/scijava/ui/swing/script/TextEditor.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,7 @@
158158
import org.scijava.thread.ThreadService;
159159
import org.scijava.ui.CloseConfirmable;
160160
import org.scijava.ui.UIService;
161+
import org.scijava.ui.swing.script.autocompletion.ClassUtil;
161162
import org.scijava.ui.swing.script.commands.ChooseFontSize;
162163
import org.scijava.ui.swing.script.commands.ChooseTabSize;
163164
import org.scijava.ui.swing.script.commands.GitGrep;

src/main/java/org/scijava/ui/swing/script/ClassUtil.java renamed to src/main/java/org/scijava/ui/swing/script/autocompletion/ClassUtil.java

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package org.scijava.ui.swing.script;
1+
package org.scijava.ui.swing.script.autocompletion;
22

33
import java.io.File;
44
import java.io.IOException;
@@ -290,6 +290,11 @@ static public final Stream<String> findClassNamesContaining(final String text) {
290290
return class_urls.keySet().stream().filter(s -> s.contains(text));
291291
}
292292

293+
/**
294+
* Find simple class names starting with "text", returning the fully qualified class names.
295+
* @param text
296+
* @return
297+
*/
293298
static public final ArrayList<String> findSimpleClassNamesStartingWith(final String text) {
294299
ensureCache();
295300
final ArrayList<String> matches = new ArrayList<>();
@@ -298,7 +303,7 @@ static public final ArrayList<String> findSimpleClassNamesStartingWith(final Str
298303
for (final String classname: class_urls.keySet()) {
299304
final int idot = classname.lastIndexOf('.');
300305
final String simplename = -1 == idot ? classname : classname.substring(idot + 1);
301-
if (simplename.startsWith(text)) matches.add(simplename);
306+
if (simplename.startsWith(text)) matches.add(classname);
302307
}
303308
return matches;
304309
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package org.scijava.ui.swing.script.autocompletion;
2+
3+
import org.fife.ui.autocomplete.BasicCompletion;
4+
import org.fife.ui.autocomplete.CompletionProvider;
5+
6+
public class ImportCompletion extends BasicCompletion
7+
{
8+
protected final String importStatement;
9+
10+
public ImportCompletion(final CompletionProvider provider, final String replacementText, final String importStatement) {
11+
super(provider, replacementText);
12+
this.importStatement = importStatement;
13+
}
14+
15+
@Override
16+
public String getSummary() {
17+
return importStatement;
18+
}
19+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package org.scijava.ui.swing.script.autocompletion;
2+
3+
public interface ImportFormat
4+
{
5+
/** Given a fully-qualified class name, return a String with the class formatted as an import statement. */
6+
public String singleToImportStatement(String className);
7+
8+
public String dualToImportStatement(String packageName, String simpleClassName);
9+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package org.scijava.ui.swing.script.autocompletion;
2+
3+
import org.fife.ui.autocomplete.AutoCompletion;
4+
import org.fife.ui.autocomplete.Completion;
5+
import org.fife.ui.autocomplete.CompletionProvider;
6+
import org.scijava.script.ScriptLanguage;
7+
import org.scijava.ui.swing.script.EditorPane;
8+
9+
public class JythonAutoCompletion extends AutoCompletion {
10+
11+
public JythonAutoCompletion(final CompletionProvider provider) {
12+
super(provider);
13+
}
14+
15+
@Override
16+
protected void insertCompletion(final Completion c, final boolean typedParamListStartChar) {
17+
if (c instanceof ImportCompletion) {
18+
final EditorPane editor = (EditorPane) super.getTextComponent();
19+
editor.beginAtomicEdit();
20+
super.insertCompletion(c, typedParamListStartChar);
21+
final ImportCompletion cc = (ImportCompletion)c;
22+
final String[] lines = editor.getText().split("\n");
23+
int count = 0;
24+
// TODO: parse imports, avoid repeats
25+
for (int i=0; i<lines.length; ++i) {
26+
final String line = lines[i];
27+
count += line.length() + 1; // +1 for the \n
28+
if (0 == line.trim().length()) continue;
29+
if ('#' == line.charAt(0)) continue;
30+
// Else, found the first non-comment non-empty line
31+
// Insert import statement above line at i
32+
editor.insert(cc.importStatement + "\n", count - line.length() - 1);
33+
break;
34+
}
35+
editor.endAtomicEdit();
36+
} else {
37+
super.insertCompletion(c, typedParamListStartChar);
38+
}
39+
}
40+
}

src/main/java/org/scijava/ui/swing/script/autocompletion/AutocompletionProvider.java renamed to src/main/java/org/scijava/ui/swing/script/autocompletion/JythonAutocompletionProvider.java

Lines changed: 16 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,15 @@
1818
import org.fife.ui.autocomplete.Completion;
1919
import org.fife.ui.autocomplete.DefaultCompletionProvider;
2020
import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea;
21-
import org.scijava.ui.swing.script.ClassUtil;
2221

23-
public class AutocompletionProvider extends DefaultCompletionProvider {
22+
public class JythonAutocompletionProvider extends DefaultCompletionProvider {
2423

2524
private final RSyntaxTextArea text_area;
25+
private final ImportFormat formatter;
2626

27-
public AutocompletionProvider(final RSyntaxTextArea text_area) {
27+
public JythonAutocompletionProvider(final RSyntaxTextArea text_area, final ImportFormat formatter) {
2828
this.text_area = text_area;
29+
this.formatter = formatter;
2930
new Thread(new Runnable() {
3031
@Override
3132
public void run() {
@@ -58,7 +59,7 @@ public boolean isValidChar(final char c) {
5859

5960
private final List<Completion> asCompletionList(final Stream<String> stream, final String pre) {
6061
return stream
61-
.map((s) -> new BasicCompletion(AutocompletionProvider.this, pre + s))
62+
.map((s) -> new BasicCompletion(JythonAutocompletionProvider.this, pre + s))
6263
.collect(Collectors.toList());
6364
}
6465

@@ -72,20 +73,11 @@ public List<Completion> getCompletionsImpl(final JTextComponent comp) {
7273
// E.g. "from ij" to expand to a package name and class like ij or ij.gui or ij.plugin
7374
final Matcher m1 = fromImport.matcher(text);
7475
if (m1.find())
75-
return asCompletionList(ClassUtil.findClassNamesContaining(m1.group(3))
76-
.map(new Function<String, String>() {
77-
@Override
78-
public final String apply(final String s) {
79-
final int idot = s.lastIndexOf('.');
80-
return "from " + s.substring(0, Math.max(0, idot)) + " import " + s.substring(idot +1);
81-
}
82-
}),
83-
"");
76+
return asCompletionList(ClassUtil.findClassNamesContaining(m1.group(3)).map(formatter::singleToImportStatement), "");
8477

8578
final Matcher m1f = fastImport.matcher(text);
8679
if (m1f.find())
87-
return asCompletionList(ClassUtil.findClassNamesForPackage(m1f.group(2)).map(s -> s.substring(m1f.group(2).length() + 1)),
88-
m1f.group(0) + "import ");
80+
return asCompletionList(ClassUtil.findClassNamesForPackage(m1f.group(2)).map(formatter::singleToImportStatement), "");
8981

9082
// E.g. "from ij.gui import Roi, Po" to expand to PolygonRoi, PointRoi for Jython
9183
// or e.g. "importClass(Package.ij" to expand to a fully qualified class name for Javascript
@@ -113,8 +105,15 @@ public final String apply(final String s) {
113105
}
114106

115107
final Matcher m3 = simpleClassName.matcher(text);
116-
if (m3.find())
117-
return asCompletionList(ClassUtil.findSimpleClassNamesStartingWith(m3.group(2)).stream(), m3.group(1));
108+
if (m3.find()) {
109+
// Side effect: insert the import at the top of the file if necessary
110+
//return asCompletionList(ClassUtil.findSimpleClassNamesStartingWith(m3.group(2)).stream(), m3.group(1));
111+
return ClassUtil.findSimpleClassNamesStartingWith(m3.group(2)).stream()
112+
.map(className -> new ImportCompletion(JythonAutocompletionProvider.this,
113+
className.substring(className.lastIndexOf('.') + 1),
114+
formatter.singleToImportStatement(className)))
115+
.collect(Collectors.toList());
116+
}
118117

119118
final Matcher m4 = staticMethodOrField.matcher(text);
120119
if (m4.find()) {
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package org.scijava.ui.swing.script.autocompletion;
2+
3+
public class JythonImportFormat implements ImportFormat
4+
{
5+
@Override
6+
public final String singleToImportStatement(final String className) {
7+
final int idot = className.lastIndexOf('.');
8+
if (-1 == idot)
9+
return "import " + className;
10+
return dualToImportStatement(className.substring(0, idot), className.substring(idot + 1));
11+
}
12+
13+
@Override
14+
public String dualToImportStatement(final String packageName, final String simpleClassName) {
15+
return "from " + packageName + " import " + simpleClassName;
16+
}
17+
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
package org.scijava.ui.swing.script.languagesupport;
2+
3+
import org.fife.rsta.ac.AbstractLanguageSupport;
4+
import org.fife.ui.autocomplete.AutoCompletion;
5+
import org.fife.ui.autocomplete.CompletionProvider;
6+
import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea;
7+
import org.scijava.plugin.Plugin;
8+
import org.scijava.ui.swing.script.LanguageSupportPlugin;
9+
import org.scijava.ui.swing.script.LanguageSupportService;
10+
import org.scijava.ui.swing.script.autocompletion.JythonAutocompletionProvider;
11+
import org.scijava.ui.swing.script.autocompletion.JythonImportFormat;
12+
import org.scijava.ui.swing.script.autocompletion.JythonAutoCompletion;
13+
14+
/**
15+
* {@link LanguageSupportPlugin} for the jython language.
16+
*
17+
* @author Albert Cardona
18+
*
19+
* @see LanguageSupportService
20+
*/
21+
@Plugin(type = LanguageSupportPlugin.class)
22+
public class JythonLanguageSupportPlugin extends AbstractLanguageSupport implements LanguageSupportPlugin
23+
{
24+
25+
private AutoCompletion ac;
26+
private RSyntaxTextArea text_area;
27+
28+
public JythonLanguageSupportPlugin() {
29+
setAutoCompleteEnabled(true);
30+
setShowDescWindow(true);
31+
}
32+
33+
@Override
34+
public String getLanguageName() {
35+
return "python";
36+
}
37+
38+
@Override
39+
public void install(final RSyntaxTextArea textArea) {
40+
this.text_area = textArea;
41+
this.ac = this.createAutoCompletion(null);
42+
this.ac.install(textArea);
43+
// store upstream
44+
super.installImpl(textArea, this.ac);
45+
}
46+
47+
@Override
48+
public void uninstall(final RSyntaxTextArea textArea) {
49+
if (textArea == this.text_area) {
50+
super.uninstallImpl(textArea); // will call this.acp.uninstall();
51+
}
52+
}
53+
54+
/**
55+
* Ignores the argument.
56+
*/
57+
@Override
58+
protected AutoCompletion createAutoCompletion(CompletionProvider p) {
59+
return new JythonAutoCompletion(new JythonAutocompletionProvider(text_area, new JythonImportFormat()));
60+
}
61+
62+
}

0 commit comments

Comments
 (0)