Skip to content

Commit

Permalink
Implement subword completion for db.sqleditor
Browse files Browse the repository at this point in the history
Closes: #7912
  • Loading branch information
matthiasblaesing committed Nov 8, 2024
1 parent 7d89336 commit 58817c3
Show file tree
Hide file tree
Showing 9 changed files with 355 additions and 65 deletions.
9 changes: 9 additions & 0 deletions ide/db.sql.editor/nbproject/project.xml
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,15 @@
<specification-version>1.27</specification-version>
</run-dependency>
</dependency>
<dependency>
<code-name-base>org.netbeans.modules.options.editor</code-name-base>
<build-prerequisite/>
<compile-dependency/>
<run-dependency>
<release-version>1</release-version>
<specification-version>1.88</specification-version>
</run-dependency>
</dependency>
<dependency>
<code-name-base>org.netbeans.modules.projectapi</code-name-base>
<build-prerequisite/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,25 +32,30 @@
*/
public final class OptionsUtils {
// package visible to allow access from unit tests
static final String PAIR_CHARACTERS_COMPLETION = "pair-characters-completion";
public static final String PAIR_CHARACTERS_COMPLETION = "pair-characters-completion"; //NOI18N
public static final String SQL_AUTO_COMPLETION_SUBWORDS = "sql-completion-subwords"; //NOI18N
public static final boolean SQL_AUTO_COMPLETION_SUBWORDS_DEFAULT = false;
private static final AtomicBoolean INITED = new AtomicBoolean(false);

private static final PreferenceChangeListener PREFERENCES_TRACKER = new PreferenceChangeListener() {
@Override
public void preferenceChange(PreferenceChangeEvent evt) {
String settingName = evt == null ? null : evt.getKey();

if (settingName == null
|| PAIR_CHARACTERS_COMPLETION.equals(settingName)) {
pairCharactersCompletion = preferences.getBoolean(
PAIR_CHARACTERS_COMPLETION, true);
if (settingName == null || PAIR_CHARACTERS_COMPLETION.equals(settingName)) {
pairCharactersCompletion = preferences.getBoolean(PAIR_CHARACTERS_COMPLETION, true);
}

if (settingName == null || SQL_AUTO_COMPLETION_SUBWORDS.equals(settingName)) {
sqlCompletionSubwords = preferences.getBoolean(SQL_AUTO_COMPLETION_SUBWORDS, SQL_AUTO_COMPLETION_SUBWORDS_DEFAULT);
}
}
};

private static Preferences preferences;

private static boolean pairCharactersCompletion = true;
private static boolean sqlCompletionSubwords = SQL_AUTO_COMPLETION_SUBWORDS_DEFAULT;

private OptionsUtils() {
}
Expand All @@ -65,6 +70,16 @@ public static boolean isPairCharactersCompletion() {
return pairCharactersCompletion;
}

/**
* Option: "Subword Completion"
*
* @return true if code completion should be subword based
*/
public static boolean isSqlCompletionSubwords() {
lazyInit();
return sqlCompletionSubwords;
}

private static void lazyInit() {
if (INITED.compareAndSet(false, true)) {
preferences = MimeLookup.getLookup(SQLLanguageConfig.mimeType).lookup(Preferences.class);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
Expand All @@ -38,6 +39,7 @@
import org.netbeans.modules.db.metadata.model.api.Tuple;
import org.netbeans.modules.db.metadata.model.api.View;
import org.netbeans.modules.db.sql.analyzer.QualIdent;
import org.netbeans.modules.db.sql.editor.OptionsUtils;
import org.netbeans.modules.db.sql.editor.api.completion.SQLCompletionResultSet;
import org.netbeans.modules.db.sql.editor.api.completion.SubstitutionHandler;
import org.netbeans.spi.editor.completion.CompletionResultSet;
Expand All @@ -48,7 +50,7 @@
*/
public class SQLCompletionItems implements Iterable<SQLCompletionItem> {

private final List<SQLCompletionItem> items = new ArrayList<SQLCompletionItem>();
private final List<SQLCompletionItem> items = new ArrayList<>();
private final Quoter quoter;
private final SubstitutionHandler substitutionHandler;

Expand All @@ -67,32 +69,28 @@ public void addKeywords(String prefix, final int substitutionOffset, String... k
}

public Set<String> addCatalogs(Metadata metadata, Set<String> restrict, String prefix, final boolean quote, final int substitutionOffset) {
Set<String> result = new TreeSet<String>();
filterMetadata(metadata.getCatalogs(), restrict, prefix, new Handler<Catalog>() {
public void handle(Catalog catalog) {
String catalogName = catalog.getName();
items.add(SQLCompletionItem.catalog(
catalogName,
doQuote(catalogName, quote),
substitutionOffset,
substitutionHandler));
}
Set<String> result = new TreeSet<>();
filterMetadata(metadata.getCatalogs(), restrict, prefix, (Catalog catalog) -> {
String catalogName = catalog.getName();
items.add(SQLCompletionItem.catalog(
catalogName,
doQuote(catalogName, quote),
substitutionOffset,
substitutionHandler));
});
return result;
}

public Set<String> addSchemas(Catalog catalog, Set<String> restrict, String prefix, final boolean quote, final int substitutionOffset) {
Set<String> result = new TreeSet<String>();
filterMetadata(catalog.getSchemas(), restrict, prefix, new Handler<Schema>() {
public void handle(Schema schema) {
if (!schema.isSynthetic()) {
String schemaName = schema.getName();
items.add(SQLCompletionItem.schema(
schemaName,
doQuote(schemaName, quote),
substitutionOffset,
substitutionHandler));
}
Set<String> result = new TreeSet<>();
filterMetadata(catalog.getSchemas(), restrict, prefix, (Schema schema) -> {
if (!schema.isSynthetic()) {
String schemaName = schema.getName();
items.add(SQLCompletionItem.schema(
schemaName,
doQuote(schemaName, quote),
substitutionOffset,
substitutionHandler));
}
});
return result;
Expand All @@ -110,17 +108,15 @@ private void addTables(Schema schema, QualIdent fullyTypedIdent, Set<String> res
final String schema4display = fullyTypedIdent == null ? "" : fullyTypedIdent.getSimpleName () + '.'; // NOI18N
final int ownOffset = fullyTypedIdent == null ? substitutionOffset :
substitutionOffset - (fullyTypedIdent.getSimpleName ().length () + 1);
filterMetadata(schema.getTables(), restrict, prefix, new Handler<Table>() {
public void handle(Table table) {
String tableName = table.getName();
items.add(SQLCompletionItem.table(
tableName,
doQuote(tableName, quote),
ownOffset,
ownHandler ?
filterMetadata(schema.getTables(), restrict, prefix, (Table table) -> {
String tableName = table.getName();
items.add(SQLCompletionItem.table(
tableName,
doQuote(tableName, quote),
ownOffset,
ownHandler ?
new ExtendedSubstitutionHandler (substitutionHandler, schema4display, " (") // NOI18N
: substitutionHandler));
}
});
}

Expand All @@ -136,26 +132,22 @@ private void addViews(Schema schema, QualIdent fullyTypedIdent, Set<String> rest
final String schema4display = fullyTypedIdent == null ? "" : fullyTypedIdent.getSimpleName () + '.'; // NOI18N
final int ownOffset = fullyTypedIdent == null ? substitutionOffset :
substitutionOffset - (fullyTypedIdent.getSimpleName ().length () + 1);
filterMetadata(schema.getViews(), restrict, prefix, new Handler<View>() {
public void handle(View view) {
String viewName = view.getName();
items.add(SQLCompletionItem.view(
viewName,
doQuote(viewName, quote),
ownOffset,
ownHandler ?
filterMetadata(schema.getViews(), restrict, prefix, (View view) -> {
String viewName = view.getName();
items.add(SQLCompletionItem.view(
viewName,
doQuote(viewName, quote),
ownOffset,
ownHandler ?
new ExtendedSubstitutionHandler (substitutionHandler, schema4display, " (") // NOI18N
: substitutionHandler));
}
});
}

public void addAliases(Map<String, QualIdent> aliases, String prefix, final boolean quote, final int substitutionOffset) {
filterMap(aliases, null, prefix, new ParamHandler<String, QualIdent>() {
public void handle(String alias, QualIdent tableName) {
// Issue 145173: do not quote aliases.
items.add(SQLCompletionItem.alias(alias, tableName, alias, substitutionOffset, substitutionHandler));
}
filterMap(aliases, null, prefix, (String alias, QualIdent tableName) -> {
// Issue 145173: do not quote aliases.
items.add(SQLCompletionItem.alias(alias, tableName, alias, substitutionOffset, substitutionHandler));
});
}

Expand All @@ -170,7 +162,7 @@ public void addColumns(Tuple tuple, String prefix, final boolean quote, final in
private void addColumns(final Tuple tuple, QualIdent fullyTypedIdent, String prefix, final boolean quote, final int substitutionOffset, final boolean ownHandler) {
Schema schema = tuple.getParent();
Catalog catalog = schema.getParent();
List<String> parts = new ArrayList<String>(3);
List<String> parts = new ArrayList<>(3);
if (!catalog.isDefault()) {
parts.add(catalog.getName());
}
Expand All @@ -183,20 +175,18 @@ private void addColumns(final Tuple tuple, QualIdent fullyTypedIdent, String pre
fullyTypedIdent.getFirstQualifier () + '.' + fullyTypedIdent.getSecondQualifier (); // NOI18N
final int ownOffset = fullyTypedIdent == null ? substitutionOffset :
substitutionOffset - (fullyTypedIdent.getFirstQualifier ().length () + fullyTypedIdent.getSecondQualifier ().length () + 2);
filterMetadata(tuple.getColumns(), null, prefix, new Handler<Column>() {
public void handle(Column column) {
String columnName = column.getName();
items.add(SQLCompletionItem.column (
tuple instanceof View,
qualTableName,
columnName,
column.getTypeName(),
doQuote(columnName, quote),
ownOffset,
ownHandler ?
filterMetadata(tuple.getColumns(), null, prefix, (Column column) -> {
String columnName = column.getName();
items.add(SQLCompletionItem.column (
tuple instanceof View,
qualTableName,
columnName,
column.getTypeName(),
doQuote(columnName, quote),
ownOffset,
ownHandler ?
new ExtendedSubstitutionHandler (substitutionHandler, table4display + " (", null) // NOI18N
: substitutionHandler));
}
});
}

Expand All @@ -208,6 +198,7 @@ public void fill(SQLCompletionResultSet resultSet) {
resultSet.addAllItems(items);
}

@Override
public Iterator<SQLCompletionItem> iterator() {
return items.iterator();
}
Expand All @@ -224,8 +215,18 @@ private static boolean startsWithIgnoreCase(String text, String prefix) {
return text.regionMatches(true, 0, prefix, 0, prefix.length());
}

private static boolean containsIgnoreCase(String text, String prefix) {
return text.toLowerCase(Locale.ROOT).contains(prefix.toLowerCase(Locale.ROOT));
}

private static boolean filter(String string, String prefix) {
return prefix == null || startsWithIgnoreCase(string, prefix);
if(prefix == null) {
return true;
} else if (OptionsUtils.isSqlCompletionSubwords()) {
return containsIgnoreCase(string, prefix);
} else {
return startsWithIgnoreCase(string, prefix);
}
}

private static <P> void filterMap(Map<String, P> strings, Set<String> restrict, String prefix, ParamHandler<String, P> handler) {
Expand Down Expand Up @@ -267,6 +268,7 @@ public ExtendedSubstitutionHandler (SubstitutionHandler handler, String prefix,
this.prefix = prefix == null ? "" : prefix;
this.postfix = postfix == null ? "" : postfix;
}
@Override
public void substituteText (JTextComponent component, int offset, String text) {
original.substituteText (component, offset, prefix + text + postfix);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -158,5 +158,23 @@
<file name="x-sql" url="SQLExample"/>
</folder>
</folder>

<folder name="Editor">
<folder name="CodeCompletion">
<folder name="text">
<folder name="x-sql">
<file name="JavaSpecific.instance">
<attr name="instanceOf" stringvalue="org.netbeans.modules.options.editor.spi.PreferencesCustomizer$Factory"/>
<attr name="instanceCreate" methodvalue="org.netbeans.modules.db.sql.editor.ui.options.CodeCompletionPanel.getCustomizerFactory"/>
<attr name="position" intvalue="100"/>
</file>
<file name="JavaSpecificCustomCustomizer.instance">
<attr name="instanceCreate" newvalue="org.netbeans.modules.db.sql.editor.ui.options.CodeCompletionPanel$CustomCustomizerImpl"/>
<attr name="position" intvalue="110"/>
</file>
</folder>
</folder>
</folder>
</folder>
</folder>
</filesystem>
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.

CodeCompletionPanel.sqlAutoCompletionSubwords.toolTipText=
CodeCompletionPanel.sqlAutoCompletionSubwords.text=&Subword completion
Loading

0 comments on commit 58817c3

Please sign in to comment.