From 6662714277d2ba0b4a5e6be254713609562f59ef Mon Sep 17 00:00:00 2001 From: Karl Tauber Date: Fri, 10 Jul 2020 10:33:10 +0200 Subject: [PATCH] Theme Editor: auto-completion improved: - reference completion shows all keys defined in current and base files - support auto-activate for value provider - do not auto-complete single choices --- .../themeeditor/FlatCompletionProvider.java | 118 +++++++++++++----- .../themeeditor/FlatThemeEditorPane.java | 6 +- .../FlatThemePropertiesSupport.java | 52 ++++++-- 3 files changed, 129 insertions(+), 47 deletions(-) diff --git a/flatlaf-theme-editor/src/main/java/com/formdev/flatlaf/themeeditor/FlatCompletionProvider.java b/flatlaf-theme-editor/src/main/java/com/formdev/flatlaf/themeeditor/FlatCompletionProvider.java index e976032a..6a7a1285 100644 --- a/flatlaf-theme-editor/src/main/java/com/formdev/flatlaf/themeeditor/FlatCompletionProvider.java +++ b/flatlaf-theme-editor/src/main/java/com/formdev/flatlaf/themeeditor/FlatCompletionProvider.java @@ -22,10 +22,13 @@ import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.util.ArrayList; +import java.util.Collections; import java.util.HashSet; import java.util.List; +import java.util.Set; import javax.swing.text.BadLocationException; import javax.swing.text.JTextComponent; +import org.fife.ui.autocomplete.BasicCompletion; import org.fife.ui.autocomplete.Completion; import org.fife.ui.autocomplete.CompletionProvider; import org.fife.ui.autocomplete.CompletionProviderBase; @@ -109,10 +112,12 @@ class FlatCompletionProvider return getValueProvider(); case '$': + case '@': return getReferenceProvider(); case ' ': case '\t': + case '#': // colors return null; } } @@ -126,7 +131,7 @@ class FlatCompletionProvider private CompletionProvider getKeyProvider() { if( keyProvider == null ) - keyProvider = new KeyCompletionProvider(); + keyProvider = KeyCompletionProvider.getInstance(); return keyProvider; } @@ -142,20 +147,25 @@ class FlatCompletionProvider return valueProvider; } - //---- class KnownKeysCompletionProvider ---------------------------------- + //---- class KeyCompletionProvider ---------------------------------------- - private static final class KnownKeysCompletionProvider - extends DefaultCompletionProvider + /** + * A completion provider for keys, which always uses all known/predefined keys. + */ + private static final class KeyCompletionProvider + extends BaseCompletionProvider { - private static KnownKeysCompletionProvider instance; + private static KeyCompletionProvider instance; - static KnownKeysCompletionProvider getInstance() { + static KeyCompletionProvider getInstance() { if( instance == null ) - instance = new KnownKeysCompletionProvider(); + instance = new KeyCompletionProvider(); return instance; } - KnownKeysCompletionProvider() { + KeyCompletionProvider() { + setAutoActivationRules( true, "." ); + // load all keys HashSet keys = new HashSet<>(); try { @@ -197,19 +207,14 @@ class FlatCompletionProvider } } - //---- class KeyCompletionProvider ---------------------------------------- + //---- class BaseCompletionProvider --------------------------------------- - private static class KeyCompletionProvider + //TODO remove if https://github.com/bobbylight/AutoComplete/issues/77 is fixed + private static class BaseCompletionProvider extends DefaultCompletionProvider { - KeyCompletionProvider() { - setParent( KnownKeysCompletionProvider.getInstance() ); - } - - @Override - protected boolean isValidChar( char ch ) { - return super.isValidChar( ch ) || ch == '.'; - } + private boolean autoActivateAfterLetters; + private String autoActivateChars; @Override public boolean isAutoActivateOkay( JTextComponent comp ) { @@ -219,36 +224,90 @@ class FlatCompletionProvider try { char ch = comp.getText( caretPosition - 1, 1 ).charAt( 0 ); - return isAutoActivateOkay( ch ); + return (autoActivateAfterLetters && Character.isLetter( ch )) || + (autoActivateChars != null && autoActivateChars.indexOf( ch ) >= 0); } catch( BadLocationException | IndexOutOfBoundsException ex ) { // ignore return false; } } - protected boolean isAutoActivateOkay( char ch ) { - return Character.isLetter( ch ) || ch == '.'; + @Override + public void setAutoActivationRules( boolean letters, String others ) { + autoActivateAfterLetters = letters; + autoActivateChars = others; } } //---- class ReferenceCompletionProvider ---------------------------------- + /** + * A completion provider for references within values. Only keys defined + * in current properties file and in base properties files are used. + */ private static class ReferenceCompletionProvider - extends KeyCompletionProvider + extends BaseCompletionProvider { + private Set lastKeys; + + ReferenceCompletionProvider() { + setAutoActivationRules( true, "$@." ); + } + @Override - protected boolean isAutoActivateOkay( char ch ) { - return ch == '$' || super.isAutoActivateOkay( ch ); + protected boolean isValidChar( char ch ) { + return super.isValidChar( ch ) || ch == '.' || ch == '$' || ch == '@'; + } + + @Override + protected List getCompletionsImpl( JTextComponent comp ) { + updateCompletions( comp ); + return super.getCompletionsImpl( comp ); + } + + @Override + public List getCompletionsAt( JTextComponent comp, Point pt ) { + updateCompletions( comp ); + return super.getCompletionsAt( comp, pt ); + } + + @Override + public List getParameterizedCompletions( JTextComponent comp ) { + updateCompletions( comp ); + return super.getParameterizedCompletions( comp ); + } + + private void updateCompletions( JTextComponent comp ) { + FlatSyntaxTextArea fsta = (FlatSyntaxTextArea) comp; + Set keys = fsta.propertiesSupport.getAllKeys(); + if( keys == lastKeys ) + return; + + completions.clear(); + for( String key : keys ) { + if( key.startsWith( "*." ) ) + continue; + + if( !key.startsWith( "@" ) ) + key = "$".concat( key ); + + completions.add( new BasicCompletion( this, key ) ); + } + Collections.sort(completions); } } //---- class ValueCompletionProvider -------------------------------------- + /** + * A completion provider for values. + */ private static class ValueCompletionProvider - extends DefaultCompletionProvider + extends BaseCompletionProvider { ValueCompletionProvider() { - setParameterizedCompletionParams( '(', ", ", ')' ); + setAutoActivationRules( true, null ); + setParameterizedCompletionParams( '(', ",", ')' ); addFunction( "rgb", "red", "0-255 or 0-100%", @@ -293,17 +352,12 @@ class FlatCompletionProvider FunctionCompletion f = new FunctionCompletion( this, name, null ) { @Override public String toString() { - return getDefinitionString().replace( "(", " (" ); + return getDefinitionString().replace( "(", " (" ).replace( ",", ", " ); } }; f.setParams( params ); addCompletion( f ); } - - @Override - public boolean isAutoActivateOkay( JTextComponent tc ) { - return false; - } } } diff --git a/flatlaf-theme-editor/src/main/java/com/formdev/flatlaf/themeeditor/FlatThemeEditorPane.java b/flatlaf-theme-editor/src/main/java/com/formdev/flatlaf/themeeditor/FlatThemeEditorPane.java index f13cd43f..7e2afde3 100644 --- a/flatlaf-theme-editor/src/main/java/com/formdev/flatlaf/themeeditor/FlatThemeEditorPane.java +++ b/flatlaf-theme-editor/src/main/java/com/formdev/flatlaf/themeeditor/FlatThemeEditorPane.java @@ -21,6 +21,7 @@ import java.awt.Color; import java.awt.Font; import java.io.File; import java.io.IOException; +import java.io.InputStream; import java.nio.charset.StandardCharsets; import java.util.List; import javax.swing.JLayer; @@ -63,8 +64,8 @@ class FlatThemeEditorPane // textArea.setUseColorOfColorTokens( true ); // theme - try { - Theme theme = Theme.load( getClass().getResourceAsStream( "light.xml" ) ); + try( InputStream in = getClass().getResourceAsStream( "light.xml" ) ) { + Theme theme = Theme.load( in ); theme.apply( textArea ); } catch( IOException ex ) { ex.printStackTrace(); @@ -79,6 +80,7 @@ class FlatThemeEditorPane // autocomplete CompletionProvider provider = new FlatCompletionProvider(); AutoCompletion ac = new AutoCompletion( provider ); + ac.setAutoCompleteSingleChoices( false ); ac.setAutoActivationEnabled( true ); ac.setParameterAssistanceEnabled( true ); ac.setChoicesWindowSize( UIScale.scale( 300 ), UIScale.scale( 400 ) ); diff --git a/flatlaf-theme-editor/src/main/java/com/formdev/flatlaf/themeeditor/FlatThemePropertiesSupport.java b/flatlaf-theme-editor/src/main/java/com/formdev/flatlaf/themeeditor/FlatThemePropertiesSupport.java index c112e78f..9fbf9e0d 100644 --- a/flatlaf-theme-editor/src/main/java/com/formdev/flatlaf/themeeditor/FlatThemePropertiesSupport.java +++ b/flatlaf-theme-editor/src/main/java/com/formdev/flatlaf/themeeditor/FlatThemePropertiesSupport.java @@ -23,9 +23,11 @@ import java.io.IOException; import java.io.InputStream; import java.io.StringReader; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Properties; +import java.util.Set; import java.util.function.Function; import javax.swing.event.DocumentEvent; import javax.swing.event.DocumentListener; @@ -50,6 +52,8 @@ class FlatThemePropertiesSupport private long[] baseFilesLastModified; private Properties[] basePropertiesCache; + private Set allKeysCache; + FlatThemePropertiesSupport( FlatSyntaxTextArea textArea ) { this.textArea = textArea; @@ -129,19 +133,7 @@ class FlatThemePropertiesSupport // look in base properties files for( int i = 0; i < baseFiles.length; i++ ) { - long lastModified = baseFiles[i].lastModified(); - if( baseFilesLastModified[i] != lastModified ) { - // (re)load base properties file - baseFilesLastModified[i] = lastModified; - basePropertiesCache[i] = new Properties(); - try( InputStream in = new FileInputStream( baseFiles[i] ) ) { - basePropertiesCache[i].load( in ); - } catch( IOException ex ) { - ex.printStackTrace(); //TODO - } - } - - value = basePropertiesCache[i].getProperty( key ); + value = getBaseProperties( i ).getProperty( key ); if( value != null ) return value; } @@ -162,9 +154,43 @@ class FlatThemePropertiesSupport return propertiesCache; } + private Properties getBaseProperties( int index ) { + long lastModified = baseFiles[index].lastModified(); + if( baseFilesLastModified[index] != lastModified || basePropertiesCache[index] == null ) { + // (re)load base properties file + baseFilesLastModified[index] = lastModified; + basePropertiesCache[index] = new Properties(); + try( InputStream in = new FileInputStream( baseFiles[index] ) ) { + basePropertiesCache[index].load( in ); + } catch( IOException ex ) { + ex.printStackTrace(); //TODO + } + } + + return basePropertiesCache[index]; + } + + Set getAllKeys() { + if( allKeysCache != null ) + return allKeysCache; + + allKeysCache = new HashSet<>(); + + for( Object key : getProperties().keySet() ) + allKeysCache.add( (String) key ); + + for( int i = 0; i < baseFiles.length; i++ ) { + for( Object key : getBaseProperties( i ).keySet() ) + allKeysCache.add( (String) key ); + } + + return allKeysCache; + } + private void clearCache() { propertiesCache = null; parsedValueCache.clear(); + allKeysCache = null; } //---- interface DocumentListener ----