3030
3131import java .io .File ;
3232import java .io .IOException ;
33+ import java .lang .reflect .Constructor ;
3334import java .lang .reflect .Parameter ;
3435import java .nio .file .FileVisitOption ;
3536import java .nio .file .Files ;
3839import java .util .Collections ;
3940import java .util .Comparator ;
4041import java .util .List ;
42+ import java .util .Map ;
4143import java .util .function .Function ;
4244import java .util .jar .JarEntry ;
4345import java .util .jar .JarFile ;
5961public class JythonAutoCompletions {
6062
6163 static private final Pattern assign = Pattern .compile ("^([ \\ t]*)(([a-zA-Z_][a-zA-Z0-9_ \\ t,]*)[ \\ t]+=[ \\ t]+(.*))$" ),
62- nameToken = Pattern .compile ("^(.*?[ \\ t]+|)([a-zA-Z_][a-zA-Z0-9_]+)$" ),
64+ nameToken = Pattern .compile ("^(.*?[ \\ t,\\ [=\\ (]+)([a-zA-Z_][a-zA-Z0-9_]+)$" ),
65+ invocation = Pattern .compile ("^(.*?[ \\ t]+|)([a-zA-Z_][a-zA-Z0-9_]+\\ ()$" ),
6366 dotNameToken = Pattern .compile ("^(.*?[ \\ t]+|)([a-zA-Z0-9_\\ .\\ [\\ ](){}]+)\\ .([a-zA-Z0-9_]*)$" ),
6467 endingCode = Pattern .compile ("^([ \\ t]*)[^#]*?(.*?)[ \\ t]*:[ \\ t]*(#.*|)[\\ n]*$" ),
6568 sysPathAppend = Pattern .compile ("sys.path.append[ \\ t]*[(][ \\ t]*['\" ](.*?)['\" ][ \\ t]*[)]" ), // fragile to line breaks in e.g. .append
@@ -113,9 +116,9 @@ public List<Completion> completionsFor(final JythonAutocompletionProvider provid
113116
114117 // Query the lastLine to find out what needs autocompletion
115118
116- // Preconditions 1: can't expand when ending with any of: "() []{},; "
119+ // Preconditions 1: can't expand when ending with any of: "[]{},; "
117120 final char lastChar = lastLine .charAt (lastLine .length () -1 );
118- if (0 == lastLine .length () || "() []{},; " .indexOf (lastChar ) > -1 )
121+ if (0 == lastLine .length () || "[]{},; " .indexOf (lastChar ) > -1 )
119122 return Collections .emptyList ();
120123
121124 // Preconditions 2: codeWithoutLastLine has to be valid
@@ -221,10 +224,57 @@ public List<Completion> completionsFor(final JythonAutocompletionProvider provid
221224 }
222225
223226 final Matcher m1 = nameToken .matcher (lastLine );
227+ /*
224228 if (m1.find())
225229 return JythonScriptParser.parseAST(codeWithoutLastLine).getLast().findStartsWith(m1.group(2)).stream()
226230 .map(s -> new BasicCompletion(provider, (lastLine + s.substring(m1.group(2).length())).substring(crop)))
227231 .collect(Collectors.toList());
232+ */
233+
234+ if (m1 .find ()) {
235+ final Scope scope = JythonScriptParser .parseAST (codeWithoutLastLine ).getLast ();
236+ // Handle argument suggestions for constructors and functions
237+ provider .setParameterChoicesProvider (new CustomParameterChoicesProvider (provider , scope ));
238+ final Map <String , String > names = scope .findStartsWith2 (m1 .group (2 ));
239+ final ArrayList <Completion > completions = new ArrayList <>();
240+ for (final Map .Entry <String , String > e : names .entrySet ()) {
241+ final String classname = e .getValue ();
242+ // Add a constructor parameterized completion
243+ if (null != classname ) {
244+ try {
245+ final Class <?> c = Class .forName (classname );
246+ for (final Constructor <?> constructor : c .getConstructors ()) {
247+ completions .add (makeDotCompletion (
248+ crop > -1 && crop < m1 .group (1 ).length () ? m1 .group (1 ).substring (crop ) : "" ,
249+ m1 .group (2 ), new CompletionText (e .getKey (), c , constructor ), provider ));
250+ }
251+ } catch (ClassNotFoundException cnfe ) {
252+ JythonDev .printTrace ("Can't load class: " + classname );
253+ }
254+ }
255+ // Add the simple classname completion
256+ completions .add (new BasicCompletion (provider , (lastLine + e .getKey ().substring (m1 .group (2 ).length ())).substring (crop )));
257+ }
258+ return completions ;
259+ }
260+
261+ JythonDev .printTrace ("invocation.matcher:" );
262+ final Matcher m1c = invocation .matcher (lastLine );
263+ if (m1c .find ()) {
264+ final String name = m1c .group (2 ).substring (0 , m1c .group (2 ).length () -1 );
265+ JythonDev .printTrace (" name: " + name );
266+ final Scope scope = JythonScriptParser .parseAST (codeWithoutLastLine ).getLast ();
267+ final DotAutocompletions da = scope .find (name , DotAutocompletions .EMPTY );
268+ provider .setParameterChoicesProvider (new CustomParameterChoicesProvider (provider , scope ));
269+ JythonDev .print (da );
270+ if (da instanceof ConstructorAutocompletions ) {
271+ JythonDev .printTrace ("da is a ConstructorAutocompletions" );
272+ return da .get ().stream ()
273+ .map (ct -> makeDotCompletion ("" , name , ct , provider )) // TODO pre is wrong: see below for hints
274+ .collect (Collectors .toList ());
275+ }
276+ // TODO and functions
277+ }
228278
229279 final Matcher m2 = dotNameToken .matcher (lastLine );
230280 if (m2 .find ()) {
@@ -264,49 +314,12 @@ public List<Completion> completionsFor(final JythonAutocompletionProvider provid
264314 final String lowerCaseSeed = seed .toLowerCase ();
265315
266316 // Depends on scope
267- provider .setParameterChoicesProvider (new ParameterChoicesProvider () {
268- @ Override
269- public List <Completion > getParameterChoices (JTextComponent tc ,
270- org .fife .ui .autocomplete .ParameterizedCompletion .Parameter param ) {
271- final Object typeObj = param .getTypeObject ();
272- Class <?> clazz = null ;
273- if (null != typeObj && typeObj instanceof Class ) {
274- clazz = (Class <?>)typeObj ;
275- JythonDev .printTrace ("class: " + clazz + ", " + clazz .getCanonicalName ());
276- }
277- switch (param .getType ()) {
278- case "int" : clazz = Integer .class ; break ;
279- case "long" : clazz = Long .class ; break ;
280- case "float" : clazz = Float .class ; break ;
281- case "double" : clazz = Double .class ; break ;
282- case "boolean" : clazz = Boolean .class ; break ;
283- case "char" : clazz = Character .class ; break ;
284- case "short" : clazz = Short .class ; break ;
285- case "byte" : clazz = Byte .class ; break ;
286- }
287- if (null != clazz ) try {
288- JythonDev .printTrace ("type is: " + param .getType ());
289- JythonDev .printTrace ("class is: " + clazz .getCanonicalName ());
290- final List <Completion > bc = scope
291- .findVarsByType (param .getType (), clazz )
292- .map (varName -> new BasicCompletion (provider , varName ))
293- .collect (Collectors .toList ());
294- for (int i =0 ; i <bc .size (); ++i ) {
295- ((BasicCompletion )bc .get (i )).setRelevance (bc .size () - i ); // ensure sorted from innermost scope to outer, rather than alphabetical
296- }
297- JythonDev .printTrace ("found bc: " + bc .size ());
298- return bc ;
299- } catch (Exception e ) {
300- JythonDev .printError (e );
301- }
302- return Collections .emptyList ();
303- }
304- });
317+ provider .setParameterChoicesProvider (new CustomParameterChoicesProvider (provider , scope ));
305318
306319 List <Completion > list = da .get ().stream ()
307320 .filter (s -> s .getReplacementText ().toLowerCase ().contains (lowerCaseSeed ))
308321 //.map(s -> s.getCompletion(provider, pre + s.getReplacementText(), s.getReplacementText().startsWith(seed) ? 1 : 0))
309- .map (s -> makeDotCompletion (pre , lowerCaseSeed , scope , s , provider ))
322+ .map (s -> makeDotCompletion (pre , lowerCaseSeed , s , provider ))
310323 .collect (Collectors .toList ());
311324 sortCompletions (list , seed );
312325 return list ;
@@ -315,13 +328,15 @@ public List<Completion> getParameterChoices(JTextComponent tc,
315328 return Collections .emptyList ();
316329 }
317330
318- private static Completion makeDotCompletion (final String pre , final String seed , final Scope scope , final CompletionText ct , final CompletionProvider provider ) {
319- List <Parameter > ps = ct .getMethodArgs ();
320- if (null != ps && !ps .isEmpty ()) {
331+ protected static Completion makeDotCompletion (final String pre , final String seed , final CompletionText ct , final CompletionProvider provider ) {
332+ final List <Parameter > ps = ct .getMethodArgs ();
333+ if (null != ps && null != ct . getReturnType ()) { // ) && !ps.isEmpty()) {
321334 JythonDev .printTrace ("using FunctionCompletion: ps is " + ps .stream ().map (Parameter ::toString ).collect (Collectors .joining (", " , "[" , "]" )));
335+ String text = ct .getReplacementText ();
336+ if (text .endsWith ("()" )) text = text .substring (0 , text .length () -2 ); // remove trailing parentheses
322337 final FunctionCompletion fc = new CustomFunctionCompletion (
323338 provider ,
324- pre + ct . getReplacementText (). substring ( 0 , ct . getReplacementText (). length () - 2 ), // remove trailing parentheses
339+ pre + text ,
325340 ct .getReturnType ());
326341 fc .setReturnValueDescription (ct .getReturnType ());
327342 fc .setShortDescription (ct .getSummary ()); // CompletionText.getDescription returns null
@@ -338,6 +353,53 @@ private static Completion makeDotCompletion(final String pre, final String seed,
338353 return ct .getCompletion (provider , pre + ct .getReplacementText (), ct .getReplacementText ().startsWith (seed ) ? 1 : 0 );
339354 }
340355
356+ private final class CustomParameterChoicesProvider implements ParameterChoicesProvider {
357+ private final JythonAutocompletionProvider provider ;
358+ private final Scope scope ;
359+
360+ private CustomParameterChoicesProvider (JythonAutocompletionProvider provider , Scope scope ) {
361+ this .provider = provider ;
362+ this .scope = scope ;
363+ }
364+
365+ @ Override
366+ public List <Completion > getParameterChoices (JTextComponent tc ,
367+ org .fife .ui .autocomplete .ParameterizedCompletion .Parameter param ) {
368+ final Object typeObj = param .getTypeObject ();
369+ Class <?> clazz = null ;
370+ if (null != typeObj && typeObj instanceof Class ) {
371+ clazz = (Class <?>)typeObj ;
372+ JythonDev .printTrace ("class: " + clazz + ", " + clazz .getCanonicalName ());
373+ }
374+ switch (param .getType ()) {
375+ case "int" : clazz = Integer .class ; break ;
376+ case "long" : clazz = Long .class ; break ;
377+ case "float" : clazz = Float .class ; break ;
378+ case "double" : clazz = Double .class ; break ;
379+ case "boolean" : clazz = Boolean .class ; break ;
380+ case "char" : clazz = Character .class ; break ;
381+ case "short" : clazz = Short .class ; break ;
382+ case "byte" : clazz = Byte .class ; break ;
383+ }
384+ if (null != clazz ) try {
385+ JythonDev .printTrace ("type is: " + param .getType ());
386+ JythonDev .printTrace ("class is: " + clazz .getCanonicalName ());
387+ final List <Completion > bc = scope
388+ .findVarsByType (param .getType (), clazz )
389+ .map (varName -> new BasicCompletion (provider , varName ))
390+ .collect (Collectors .toList ());
391+ for (int i =0 ; i <bc .size (); ++i ) {
392+ ((BasicCompletion )bc .get (i )).setRelevance (bc .size () - i ); // ensure sorted from innermost scope to outer, rather than alphabetical
393+ }
394+ JythonDev .printTrace ("found bc: " + bc .size ());
395+ return bc ;
396+ } catch (Exception e ) {
397+ JythonDev .printError (e );
398+ }
399+ return Collections .emptyList ();
400+ }
401+ }
402+
341403
342404 @ SuppressWarnings ("unused" )
343405 private static String removeLastOptionalDot (final String s ) {
0 commit comments