JSAutoCompleteProvier revamp:

-Type resolution now happens directly in ScriptDocument

 -Extends JavaAutocompleteProvider

 -Removed redundant code/logic
This commit is contained in:
bigguy345 committed 2026-01-04 22:41:00 +02:00
1 parent 1221badc69
commit bbe6d9bb77
3 files changed
+124 -209

No files matched your search

@@ -390,10 +390,7 @@ public class AutocompleteManager {
// Get suggestions from appropriate provider
AutocompleteProvider provider = document.isJavaScript() ? jsProvider : javaProvider;
// For JavaScript, update variable types from analyzer
if (document.isJavaScript() && document.getJSAnalyzer() != null) {
jsProvider.updateVariableTypes(document.getJSAnalyzer().getVariableTypes());
}
// No need to update variable types - JSAutocompleteProvider now gets them directly from ScriptDocument
List<AutocompleteItem> suggestions = provider.getSuggestions(context);
@@ -1,22 +1,21 @@
package noppes.npcs.client.gui.util.script.autocomplete;
import noppes.npcs.client.gui.util.script.interpreter.ScriptDocument;
import noppes.npcs.client.gui.util.script.interpreter.field.FieldInfo;
import noppes.npcs.client.gui.util.script.interpreter.js_parser.*;
import noppes.npcs.client.gui.util.script.interpreter.method.MethodInfo;
import noppes.npcs.client.gui.util.script.interpreter.type.TypeInfo;
import java.util.*;
/**
* Autocomplete provider for JavaScript/ECMAScript scripts.
* Uses JSTypeRegistry for type resolution.
* Uses ScriptDocument's unified TypeInfo/TypeResolver system for type resolution.
*/
public class JSAutocompleteProvider implements AutocompleteProvider {
public class JSAutocompleteProvider extends JavaAutocompleteProvider {
private ScriptDocument document;
private final JSTypeRegistry registry;
// Variable types inferred from the document
private Map<String, String> variableTypes = new HashMap<>();
public JSAutocompleteProvider() {
this.registry = JSTypeRegistry.getInstance();
if (!registry.isInitialized()) {
@@ -24,17 +23,6 @@ public class JSAutocompleteProvider implements AutocompleteProvider {
}
}
public void setDocument(ScriptDocument document) {
this.document = document;
}
/**
* Update the variable type map from document analysis.
*/
public void updateVariableTypes(Map<String, String> types) {
this.variableTypes = types != null ? new HashMap<>(types) : new HashMap<>();
}
@Override
public boolean canProvide(Context context) {
return document != null && document.isJavaScript();
@@ -43,13 +31,13 @@ public class JSAutocompleteProvider implements AutocompleteProvider {
@Override
public List<AutocompleteItem> getSuggestions(Context context) {
List<AutocompleteItem> items = new ArrayList<>();
// Resolve owner type for usage tracking
// Resolve owner type for usage tracking using ScriptDocument's unified type resolution
String ownerFullName = null;
if (context.isMemberAccess && context.receiverExpression != null) {
String receiverType = resolveReceiverType(context.receiverExpression);
if (receiverType != null) {
ownerFullName = receiverType;
TypeInfo receiverType = document.resolveExpressionType(context.receiverExpression, context.prefixStart);
if (receiverType != null && receiverType.isResolved()) {
ownerFullName = receiverType.getFullName();
}
}
@@ -62,7 +50,7 @@ public class JSAutocompleteProvider implements AutocompleteProvider {
}
// Filter and score by prefix, then apply usage boosts
filterAndScore(items, context.prefix, context.isMemberAccess, ownerFullName);
filterAndScore(items, context.prefix, context.isMemberAccess, false, ownerFullName);
// Sort by score
Collections.sort(items);
@@ -77,35 +65,75 @@ public class JSAutocompleteProvider implements AutocompleteProvider {
/**
* Add suggestions for member access (after dot).
* Uses ScriptDocument's unified type resolution system.
*/
private void addMemberSuggestions(Context context, List<AutocompleteItem> items) {
protected void addMemberSuggestions(Context context, List<AutocompleteItem> items) {
String receiverExpr = context.receiverExpression;
if (receiverExpr == null || receiverExpr.isEmpty()) {
return;
}
// Infer type of receiver
String receiverType = inferExpressionType(receiverExpr);
if (receiverType == null) {
// Use ScriptDocument's resolveExpressionType - handles both Java and JS
TypeInfo receiverType = document.resolveExpressionType(receiverExpr, context.prefixStart);
if (receiverType == null || !receiverType.isResolved()) {
return;
}
JSTypeInfo typeInfo = registry.getType(receiverType);
if (typeInfo == null) {
// For JS types, check JSTypeRegistry
JSTypeInfo jsTypeInfo = registry.getType(receiverType.getSimpleName());
if (jsTypeInfo != null) {
// Add methods
addMethodsFromType(jsTypeInfo, items, new HashSet<>());
// Add fields
addFieldsFromType(jsTypeInfo, items, new HashSet<>());
return;
}
// Add methods
addMethodsFromType(typeInfo, items, new HashSet<>());
// Add fields
addFieldsFromType(typeInfo, items, new HashSet<>());
// For Java types with Java class, use reflection to get members
Class<?> javaClass = receiverType.getJavaClass();
if (javaClass != null) {
try {
// Add methods from Java reflection
for (java.lang.reflect.Method method : javaClass.getMethods()) {
String methodName = method.getName();
StringBuilder signature = new StringBuilder();
signature.append(methodName).append("(");
Class<?>[] params = method.getParameterTypes();
for (int i = 0; i < params.length; i++) {
if (i > 0)
signature.append(", ");
signature.append(params[i].getSimpleName());
}
signature.append(")");
items.add(new AutocompleteItem.Builder()
.name(methodName)
.insertText(methodName)
.kind(AutocompleteItem.Kind.METHOD)
.typeLabel(method.getReturnType().getSimpleName())
.signature(signature.toString())
.build());
}
// Add fields from Java reflection
for (java.lang.reflect.Field field : javaClass.getFields()) {
items.add(new AutocompleteItem.Builder()
.name(field.getName())
.insertText(field.getName())
.kind(AutocompleteItem.Kind.FIELD)
.typeLabel(field.getType().getSimpleName())
.build());
}
} catch (SecurityException e) {
// Can't access members, skip
}
}
}
/**
* Recursively add methods from a type and its parents.
*/
private void addMethodsFromType(JSTypeInfo type, List<AutocompleteItem> items, Set<String> added) {
protected void addMethodsFromType(JSTypeInfo type, List<AutocompleteItem> items, Set<String> added) {
for (JSMethodInfo method : type.getMethods().values()) {
String name = method.getName();
// Skip overload markers (name$1, name$2, etc.)
@@ -127,7 +155,7 @@ public class JSAutocompleteProvider implements AutocompleteProvider {
/**
* Recursively add fields from a type and its parents.
*/
private void addFieldsFromType(JSTypeInfo type, List<AutocompleteItem> items, Set<String> added) {
protected void addFieldsFromType(JSTypeInfo type, List<AutocompleteItem> items, Set<String> added) {
for (JSFieldInfo field : type.getFields().values()) {
if (!added.contains(field.getName())) {
added.add(field.getName());
@@ -143,131 +171,55 @@ public class JSAutocompleteProvider implements AutocompleteProvider {
/**
* Add suggestions based on current scope (not after a dot).
* Uses ScriptDocument's unified data structures for variables and functions.
*/
private void addScopeSuggestions(Context context, List<AutocompleteItem> items) {
protected void addScopeSuggestions(Context context, List<AutocompleteItem> items) {
int pos = context.cursorPosition;
// Find containing method
MethodInfo containingMethod = document.findContainingMethod(pos);
// Add local variables
for (Map.Entry<String, String> entry : variableTypes.entrySet()) {
String varName = entry.getKey();
String varType = entry.getValue();
if (containingMethod != null) {
Map<String, FieldInfo> locals = document.getLocalsForMethod(containingMethod);
if (locals != null) {
for (FieldInfo local : locals.values()) {
if (local.isVisibleAt(pos)) {
items.add(AutocompleteItem.fromField(local));
}
}
}
// Add method parameters
for (FieldInfo param : containingMethod.getParameters()) {
items.add(AutocompleteItem.fromField(param));
}
}
// Add global fields
for (FieldInfo globalField : document.getGlobalFields().values()) {
if (globalField.isVisibleAt(pos)) {
items.add(AutocompleteItem.fromField(globalField));
}
}
// Add script-defined methods
for (MethodInfo method : document.getAllMethods()) {
items.add(AutocompleteItem.fromMethod(method));
items.add(new AutocompleteItem.Builder()
.name(varName)
.insertText(varName)
.kind(AutocompleteItem.Kind.VARIABLE)
.typeLabel(varType != null ? varType : "any")
.build());
}
// Add hook functions
for (String hookName : registry.getHookNames()) {
List<JSTypeRegistry.HookSignature> sigs = registry.getHookSignatures(hookName);
if (!sigs.isEmpty()) {
JSTypeRegistry.HookSignature sig = sigs.get(0);
items.add(new AutocompleteItem.Builder()
.name(hookName)
.insertText(hookName)
.kind(AutocompleteItem.Kind.METHOD)
.typeLabel("hook")
.signature("function " + hookName + "(e: " + sig.paramType + ")")
.documentation(sig.doc)
.build());
}
}
// Add global types
for (String typeName : registry.getTypeNames()) {
JSTypeInfo type = registry.getType(typeName);
if (type != null) {
items.add(new AutocompleteItem.Builder()
.name(typeName)
.insertText(typeName)
.kind(AutocompleteItem.Kind.CLASS)
.typeLabel("interface")
.build());
}
}
// Add JavaScript keywords
addJSKeywords(items);
addKeywords(items);
}
/**
* Infer the type of an expression.
*/
private String inferExpressionType(String expr) {
return resolveReceiverType(expr);
}
/**
* Resolve the type of a receiver expression for member access.
*/
private String resolveReceiverType(String expr) {
if (expr == null || expr.isEmpty()) {
return null;
}
expr = expr.trim();
// Check if it's a known variable
if (variableTypes.containsKey(expr)) {
return variableTypes.get(expr);
}
// Handle method chain: x.y.z() -> resolve step by step
if (expr.contains(".")) {
String[] parts = expr.split("\\.");
String currentType = null;
// Start with the first part
if (variableTypes.containsKey(parts[0])) {
currentType = variableTypes.get(parts[0]);
}
if (currentType == null) {
return null;
}
// Walk through the chain
for (int i = 1; i < parts.length; i++) {
String member = parts[i];
// Remove () for method calls
if (member.endsWith("()")) {
member = member.substring(0, member.length() - 2);
}
JSTypeInfo typeInfo = registry.getType(currentType);
if (typeInfo == null) {
return null;
}
// Check method
JSMethodInfo method = typeInfo.getMethod(member);
if (method != null) {
currentType = method.getReturnType();
continue;
}
// Check field
JSFieldInfo field = typeInfo.getField(member);
if (field != null) {
currentType = field.getType();
continue;
}
// Unknown member
return null;
}
return currentType;
}
return null;
}
/**
* Add JavaScript keywords.
*/
private void addJSKeywords(List<AutocompleteItem> items) {
protected void addKeywords(List<AutocompleteItem> items) {
String[] keywords = {
"function", "var", "let", "const", "if", "else", "for", "while", "do",
"switch", "case", "break", "continue", "return", "try", "catch", "finally",
@@ -280,44 +232,8 @@ public class JSAutocompleteProvider implements AutocompleteProvider {
items.add(AutocompleteItem.keyword(keyword));
}
}
/**
* Filter items by prefix, calculate match scores, and apply usage boosts.
*/
private void filterAndScore(List<AutocompleteItem> items, String prefix,
boolean isMemberAccess, String ownerFullName) {
UsageTracker tracker = UsageTracker.getJSInstance();
if (prefix == null || prefix.isEmpty()) {
for (AutocompleteItem item : items) {
item.calculateMatchScore("");
applyUsageBoost(item, tracker, ownerFullName);
}
return;
}
// For non-member access (first word), require strict prefix matching
// For member access (after dot), allow fuzzy/contains matching
boolean requirePrefix = !isMemberAccess;
Iterator<AutocompleteItem> iter = items.iterator();
while (iter.hasNext()) {
AutocompleteItem item = iter.next();
int score = item.calculateMatchScore(prefix, requirePrefix);
if (score < 0) {
iter.remove();
} else {
applyUsageBoost(item, tracker, ownerFullName);
}
}
}
/**
* Apply usage-based score boost to an item.
*/
private void applyUsageBoost(AutocompleteItem item, UsageTracker tracker, String ownerFullName) {
int usageCount = tracker.getUsageCount(item, ownerFullName);
int boost = UsageTracker.calculateUsageBoost(usageCount);
item.addScoreBoost(boost);
protected UsageTracker getUsageTracker() {
return UsageTracker.getJSInstance();
}
}
@@ -19,7 +19,7 @@ import java.util.*;
*/
public class JavaAutocompleteProvider implements AutocompleteProvider {
private ScriptDocument document;
protected ScriptDocument document;
public void setDocument(ScriptDocument document) {
this.document = document;
@@ -71,7 +71,7 @@ public class JavaAutocompleteProvider implements AutocompleteProvider {
/**
* Add suggestions for member access (after dot).
*/
private void addMemberSuggestions(Context context, List<AutocompleteItem> items) {
protected void addMemberSuggestions(Context context, List<AutocompleteItem> items) {
String receiverExpr = context.receiverExpression;
if (receiverExpr == null || receiverExpr.isEmpty()) {
return;
@@ -132,7 +132,7 @@ public class JavaAutocompleteProvider implements AutocompleteProvider {
/**
* Add members from a script-defined type.
*/
private void addScriptTypeMembers(ScriptTypeInfo scriptType, List<AutocompleteItem> items, boolean isStaticContext) {
protected void addScriptTypeMembers(ScriptTypeInfo scriptType, List<AutocompleteItem> items, boolean isStaticContext) {
// Add methods (getMethods returns Map<String, List<MethodInfo>>)
for (List<MethodInfo> overloads : scriptType.getMethods().values()) {
for (MethodInfo method : overloads) {
@@ -181,7 +181,7 @@ public class JavaAutocompleteProvider implements AutocompleteProvider {
/**
* Add suggestions based on current scope (not after a dot).
*/
private void addScopeSuggestions(Context context, List<AutocompleteItem> items) {
protected void addScopeSuggestions(Context context, List<AutocompleteItem> items) {
int pos = context.cursorPosition;
// Find containing method
@@ -256,7 +256,7 @@ public class JavaAutocompleteProvider implements AutocompleteProvider {
* Add suggestions for unimported classes that match the prefix.
* These will trigger auto-import when selected.
*/
private void addUnimportedClassSuggestions(String prefix, List<AutocompleteItem> items) {
protected void addUnimportedClassSuggestions(String prefix, List<AutocompleteItem> items) {
// Get the type resolver
TypeResolver resolver = TypeResolver.getInstance();
// Find classes matching this prefix (not just exact matches)
@@ -312,7 +312,7 @@ public class JavaAutocompleteProvider implements AutocompleteProvider {
/**
* Add Java keywords.
*/
private void addKeywords(List<AutocompleteItem> items) {
protected void addKeywords(List<AutocompleteItem> items) {
String[] keywords = {
"if", "else", "for", "while", "do", "switch", "case", "break", "continue",
"return", "try", "catch", "finally", "throw", "throws", "new", "this", "super",
@@ -331,7 +331,7 @@ public class JavaAutocompleteProvider implements AutocompleteProvider {
* Find the enclosing script type at a position.
* This is a workaround since findEnclosingScriptType is package-private.
*/
private ScriptTypeInfo findEnclosingType(int position) {
protected ScriptTypeInfo findEnclosingType(int position) {
for (ScriptTypeInfo type : document.getScriptTypesMap().values()) {
if (type.containsPosition(position)) {
return type;
@@ -344,18 +344,20 @@ public class JavaAutocompleteProvider implements AutocompleteProvider {
* Check if the receiver expression represents static access (class type).
* Similar logic to FieldChainMarker.isStaticContext().
*/
private boolean isStaticAccess(String receiverExpr, int position) {
protected boolean isStaticAccess(String receiverExpr, int position) {
// Try to resolve the receiver expression as a type
TypeInfo typeCheck = document.resolveType(receiverExpr);
return typeCheck != null && typeCheck.isResolved();
}
protected UsageTracker getUsageTracker() {
return UsageTracker.getJavaInstance();
}
/**
* Filter items by prefix, calculate match scores, apply usage boosts, and penalize static members in instance contexts.
*/
private void filterAndScore(List<AutocompleteItem> items, String prefix,
protected void filterAndScore(List<AutocompleteItem> items, String prefix,
boolean isMemberAccess, boolean isStaticContext, String ownerFullName) {
UsageTracker tracker = UsageTracker.getJavaInstance();
UsageTracker tracker = getUsageTracker();
if (prefix == null || prefix.isEmpty()) {
// No filtering needed, all items get a base score + usage boost
@@ -390,7 +392,7 @@ public class JavaAutocompleteProvider implements AutocompleteProvider {
/**
* Apply usage-based score boost to an item.
*/
private void applyUsageBoost(AutocompleteItem item, UsageTracker tracker, String ownerFullName) {
protected void applyUsageBoost(AutocompleteItem item, UsageTracker tracker, String ownerFullName) {
int usageCount = tracker.getUsageCount(item, ownerFullName);
int boost = UsageTracker.calculateUsageBoost(usageCount);
item.addScoreBoost(boost);
@@ -402,7 +404,7 @@ public class JavaAutocompleteProvider implements AutocompleteProvider {
* accessing through an instance (e.g., Minecraft.getMinecraft().getMinecraft()).
* However, if the item is a very strong match (exact prefix), don't penalize as much.
*/
private void applyStaticPenalty(AutocompleteItem item, boolean isMemberAccess, boolean isStaticContext) {
protected void applyStaticPenalty(AutocompleteItem item, boolean isMemberAccess, boolean isStaticContext) {
// Only apply penalty in member access contexts (after dot)
if (!isMemberAccess) {
return;
@@ -434,7 +436,7 @@ public class JavaAutocompleteProvider implements AutocompleteProvider {
* Apply penalty to inherited Object methods to push them to bottom.
* Strong matches get lighter penalty, weak matches get pushed all the way down.
*/
private void applyObjectMethodPenalty(AutocompleteItem item) {
protected void applyObjectMethodPenalty(AutocompleteItem item) {
if (!item.isInheritedObjectMethod()) {
return;
}