Heavily improved global/local/ this.field syntax highlight detection

This commit is contained in:
bigguy345 committed 2025-12-21 13:43:47 +02:00
1 parent 91a6a166bb
commit b3a64f92d7
2 files changed
+97 -4

No files matched your search

@@ -95,12 +95,20 @@ public class JavaTextContainer extends TextContainer {
// Extract method blocks first
methodBlocks = MethodBlock.collectMethodBlocks(text);
// Get excluded ranges (strings and comments) for the entire text
List<int[]> excludedRanges = MethodBlock.getExcludedRanges(text);
// Global fields (excluding those inside methods)
// Global fields (excluding those inside methods, strings, and comments)
Matcher mGlobal = GLOBAL_FIELD_DECL.matcher(text);
while (mGlobal.find()) {
String varName = mGlobal.group(2);
int varPosition = mGlobal.start(2);
// Skip if inside a string or comment
if (isInExcludedRange(varPosition, excludedRanges)) {
continue;
}
// Check if this variable is inside a method
boolean isInsideMethod = false;
@@ -157,6 +165,35 @@ public class JavaTextContainer extends TextContainer {
"Boolean", "Long", "Byte", "Short", "Character", "List", "Map", "Set"
));
// First pass: handle field accesses (this.field and obj.field patterns)
Pattern thisFieldPattern = Pattern.compile("\\bthis\\s*\\.\\s*([a-zA-Z_][a-zA-Z0-9_]*)");
Matcher thisFieldMatcher = thisFieldPattern.matcher(text);
while (thisFieldMatcher.find()) {
String fieldName = thisFieldMatcher.group(1);
int fieldPos = thisFieldMatcher.start(1);
// Highlight as GLOBAL_FIELD if it exists, UNDEFINED_VAR otherwise
if (globalFields.contains(fieldName)) {
marks.add(new Mark(fieldPos, thisFieldMatcher.end(1), TokenType.GLOBAL_FIELD));
} else {
marks.add(new Mark(fieldPos, thisFieldMatcher.end(1), TokenType.UNDEFINED_VAR));
}
}
// Handle identifier.field patterns (e.g., obj.field, global.field)
// The field part should be highlighted as GLOBAL_FIELD (light blue)
Pattern objFieldPattern = Pattern.compile("\\b([a-zA-Z_][a-zA-Z0-9_]*)\\s*\\.\\s*([a-zA-Z_][a-zA-Z0-9_]*)");
Matcher objFieldMatcher = objFieldPattern.matcher(text);
while (objFieldMatcher.find()) {
String fieldName = objFieldMatcher.group(2);
int fieldPos = objFieldMatcher.start(2);
// Skip if this is a "this.field" pattern (already handled above)
String objName = objFieldMatcher.group(1);
if (!objName.equals("this")) {
// Highlight the field part as GLOBAL_FIELD (represents another object's field)
marks.add(new Mark(fieldPos, objFieldMatcher.end(2), TokenType.GLOBAL_FIELD));
}
}
Pattern identifier = Pattern.compile("\\b([a-zA-Z_][a-zA-Z0-9_]*)\\b");
Matcher m = identifier.matcher(text);
while (m.find()) {
@@ -177,10 +214,11 @@ public class JavaTextContainer extends TextContainer {
MethodBlock methodBlock = findMethodBlockAtPosition(position);
if (methodBlock != null) {
// Inside a method - check in order: parameter, local, global
// Inside a method - check in order: parameter, local (position-aware), global
if (methodBlock.parameters.contains(name)) {
marks.add(new Mark(m.start(1), m.end(1), TokenType.PARAMETER));
} else if (methodBlock.localVariables.contains(name)) {
} else if (methodBlock.isLocalDeclaredAtPosition(name, position)) {
// Local variable is declared at or before this position
marks.add(new Mark(m.start(1), m.end(1), TokenType.LOCAL_FIELD));
} else if (globalFields.contains(name)) {
marks.add(new Mark(m.start(1), m.end(1), TokenType.GLOBAL_FIELD));
@@ -249,6 +287,18 @@ public class JavaTextContainer extends TextContainer {
return false;
}
/**
* Check if position is in an excluded range (string or comment)
*/
private boolean isInExcludedRange(int pos, List<int[]> ranges) {
for (int[] range : ranges) {
if (pos >= range[0] && pos < range[1]) {
return true;
}
}
return false;
}
public void formatCodeText() {
// Step 1: Tokenize the full text
List<Mark> marks = new ArrayList<>();
@@ -1,7 +1,9 @@
package noppes.npcs.client.gui.util.script;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -26,6 +28,7 @@ public class MethodBlock {
public String text;
public List<String> localVariables = new ArrayList<>();
public List<String> parameters = new ArrayList<>(); // Method parameters
public Map<String, Integer> localVarPositions = new HashMap<>(); // Track declaration positions
public MethodBlock(int start, int end, String text) {
this.startOffset = start;
@@ -63,14 +66,54 @@ public class MethodBlock {
// Extract local variables declared within this method
private void extractLocalVariables() {
localVariables.clear();
localVarPositions.clear();
// Get excluded ranges (strings and comments) for this method's text
List<int[]> excludedRanges = getExcludedRanges(text);
Matcher m = LOCAL_VAR_DECL.matcher(text);
while (m.find()) {
String varName = m.group(2);
if (!localVariables.contains(varName)) {
int declPosition = m.start();
// Skip if inside a string or comment
if (isInExcludedRange(declPosition, excludedRanges)) {
continue;
}
// Check if this is a "this.field" assignment - if so, skip it
// Look backwards from the declaration position to see if preceded by "this."
boolean isThisFieldAssignment = false;
if (declPosition >= 5) {
int checkStart = Math.max(0, declPosition - 10);
String before = text.substring(checkStart, declPosition);
if (before.matches(".*\\bthis\\s*\\.\\s*$")) {
isThisFieldAssignment = true;
}
}
if (!isThisFieldAssignment && !localVariables.contains(varName)) {
localVariables.add(varName);
// Store the absolute position in the full text (startOffset + relative position)
localVarPositions.put(varName, startOffset + declPosition);
}
}
}
// Check if a reference position comes before the local variable declaration
// This handles cases like: global.field on line 7, then var global = "x" on line 9
// At line 7, the local 'global' doesn't exist yet, so it should refer to the global 'global'
public boolean isLocalDeclaredAtPosition(String varName, int absolutePosition) {
if (!localVariables.contains(varName)) {
return false;
}
Integer declPos = localVarPositions.get(varName);
if (declPos == null) {
return false;
}
// The local variable is only "in scope" at or after its declaration position
return absolutePosition >= declPos;
}
// Check if a position (in the full text) falls within this method
public boolean containsPosition(int position) {