Fixed imports not resolving

if wrapped to new line
This commit is contained in:
bigguy345 committed 2025-12-22 17:39:56 +02:00
1 parent 01e2c354d5
commit 83f45fe6b8
3 files changed
+121 -72

No files matched your search

@@ -62,24 +62,27 @@ public class ClassPathFinder {
public final String classPortion; // The class part (e.g., "List" or "IOverlay.ColorType")
public final List<ClassSegment> classSegments; // Each class/inner-class segment with its type
public final int invalidStartOffset; // Character offset where invalid portion starts (-1 if all valid or all invalid)
public final int packageSegmentCount; // Number of dot-separated identifier tokens that belong to the package portion
private ResolveResult(boolean found, ClassInfo classInfo, String packagePortion,
String classPortion, List<ClassSegment> classSegments, int invalidStartOffset) {
String classPortion, List<ClassSegment> classSegments, int invalidStartOffset,
int packageSegmentCount) {
this.found = found;
this.classInfo = classInfo;
this.packagePortion = packagePortion;
this.classPortion = classPortion;
this.classSegments = classSegments != null ? classSegments : Collections.emptyList();
this.invalidStartOffset = invalidStartOffset;
this.packageSegmentCount = packageSegmentCount;
}
public static ResolveResult notFound(String packagePortion, String classPortion, int invalidStartOffset) {
return new ResolveResult(false, null, packagePortion, classPortion, null, invalidStartOffset);
public static ResolveResult notFound(String packagePortion, String classPortion, int invalidStartOffset, int packageSegmentCount) {
return new ResolveResult(false, null, packagePortion, classPortion, null, invalidStartOffset, packageSegmentCount);
}
public static ResolveResult found(ClassInfo info, String packagePortion,
String classPortion, List<ClassSegment> segments) {
return new ResolveResult(true, info, packagePortion, classPortion, segments, -1);
String classPortion, List<ClassSegment> segments, int packageSegmentCount) {
return new ResolveResult(true, info, packagePortion, classPortion, segments, -1, packageSegmentCount);
}
}
@@ -113,15 +116,20 @@ public class ClassPathFinder {
*/
public ResolveResult resolve(String importPath) {
if (importPath == null || importPath.isEmpty()) {
return ResolveResult.notFound("", "", -1);
return ResolveResult.notFound("", "", -1, 0);
}
// Normalize whitespace/newlines around dots so wrapped imports like
// "kamkeel.npcdbc.api.client.overlay.IOverlay.\n TextureFunction" become
// "kamkeel.npcdbc.api.client.overlay.IOverlay.TextureFunction" before resolving.
String normalizedPath = importPath.replaceAll("\\s*\\.\\s*", ".").trim();
// Handle trailing dot (user is still typing a package path like "java.util.")
boolean trailingDot = importPath.endsWith(".");
String pathToResolve = trailingDot ? importPath.substring(0, importPath.length() - 1) : importPath;
boolean trailingDot = normalizedPath.endsWith(".");
String pathToResolve = trailingDot ? normalizedPath.substring(0, normalizedPath.length() - 1) : normalizedPath;
if (pathToResolve.isEmpty()) {
return ResolveResult.notFound("", "", -1);
return ResolveResult.notFound("", "", -1, 0);
}
String[] segments = pathToResolve.split("\\.");
@@ -136,18 +144,18 @@ public class ClassPathFinder {
int longestValid = findLongestValidPackagePrefixIndex(segments);
if (longestValid == n) {
// Entire path is a valid package (or cached as valid)
return ResolveResult.notFound(pathToResolve, "", -1);
return ResolveResult.notFound(pathToResolve, "", -1, n);
}
if (longestValid > 0) {
String validPrefix = buildPackagePortion(segments, longestValid);
String invalidRemainder = buildClassPortion(segments, longestValid, n);
int invalidOffset = validPrefix.length() + 1; // after the dot
return ResolveResult.notFound(validPrefix, invalidRemainder, invalidOffset);
return ResolveResult.notFound(validPrefix, invalidRemainder, invalidOffset, longestValid);
}
// Nothing validated
return ResolveResult.notFound("", pathToResolve, 0);
return ResolveResult.notFound("", pathToResolve, 0, 0);
}
// Trailing dot is handled above for lowercase-only paths; for mixed-case paths
@@ -180,15 +188,15 @@ public class ClassPathFinder {
// Calculate where the invalid portion starts (after valid class + dot)
int validEndOffset = packagePortion.length() + (packagePortion.isEmpty() ? 0 : 1) + validClassPortion.length() + 1;
return new ResolveResult(false, info, packagePortion,
fullClassPortion, classSegments, validEndOffset);
fullClassPortion, classSegments, validEndOffset, classStartIdx);
}
// Full resolution successful
List<ClassSegment> classSegments = buildClassSegments(segments, classStartIdx, n);
return ResolveResult.found(
return ResolveResult.found(
new ClassInfo(info.resolvedName, info.type, packagePortion.length()),
packagePortion, validClassPortion, classSegments
);
packagePortion, validClassPortion, classSegments, classStartIdx
);
}
}
}
@@ -208,22 +216,22 @@ public class ClassPathFinder {
// Found a divergence from a known valid package
String validPrefix = buildPackagePortion(segments, typoIndex);
int invalidOffset = validPrefix.isEmpty() ? 0 : validPrefix.length() + 1;
return ResolveResult.notFound(validPrefix, buildClassPortion(segments, typoIndex, n), invalidOffset);
return ResolveResult.notFound(validPrefix, buildClassPortion(segments, typoIndex, n), invalidOffset, typoIndex);
}
// No typo detected - assume package is valid, only class portion is invalid
int invalidOffset = packagePortion.length() + 1; // After the package dot
return ResolveResult.notFound(packagePortion, classPortion, invalidOffset);
return ResolveResult.notFound(packagePortion, classPortion, invalidOffset, firstUpperIndex);
} else if (firstUpperIndex == 0) {
// Entire path starts with uppercase (e.g., "String" or "MyClass.Inner")
// Treat as class with no package
return ResolveResult.notFound("", pathToResolve, 0);
return ResolveResult.notFound("", pathToResolve, 0, 0);
} else {
// All lowercase, no uppercase found - entire path is package-like
// If trailing dot was handled above, this is just a package without class
return ResolveResult.notFound(pathToResolve, "", -1);
return ResolveResult.notFound(pathToResolve, "", -1, n);
}
}
@@ -171,7 +171,7 @@ public class IndentHelper {
settings.wrapComments = true;
// Use 75% of viewport width as max line length for some margin
int charWidth = 6; // Approximate character width
settings.maxLineLength = Math.max(60, (int)(viewportWidth * 0.75 / charWidth));
settings.maxLineLength = Math.max(60, (int)(viewportWidth * 0.8f / charWidth));
}
String formatted = helper.format(indented);
@@ -37,9 +37,11 @@ public class JavaTextContainer extends TextContainer {
public static final Pattern NUMBER = Pattern.compile(
"\\b-?(?:0[xX][\\dA-Fa-f]+|0[bB][01]+|0[oO][0-7]+|\\d*\\.?\\d+(?:[Ee][+-]?\\d+)?(?:[fFbBdDlLsS])?|NaN|null|Infinity|true|false)\\b");
// Import statement: import [static] package.path.ClassName [.*]; (semicolon optional for live typing)
public static final Pattern IMPORT = Pattern.compile(
"(?m)\\bimport\\s+(?:static\\s+)?([a-zA-Z_][a-zA-Z0-9_.]*?)(?:\\s*\\.\\s*\\*)?\\s*(?:;|$)");
// Import statement: import [static] package.path.ClassName [.*]; (semicolon optional for live typing)
// Allows optional whitespace (including newlines) around the dots so wrapped imports match.
// Match imports while allowing optional whitespace/newlines around dots.
public static final Pattern IMPORT = Pattern.compile(
"(?m)\\bimport\\s+(?:static\\s+)?([A-Za-z_][A-Za-z0-9_]*(?:\\s*\\.\\s*[A-Za-z_][A-Za-z0-9_]*)*)(?:\\s*\\.\\s*\\*)?\\s*(?:;|$)");
// ClassPathFinder for resolving imports and determining class types
private final ClassPathFinder classPathFinder = new ClassPathFinder();
@@ -51,6 +53,26 @@ public class JavaTextContainer extends TextContainer {
super(text);
}
// Map an offset into the normalized path (tokens joined with '.') back to
// an absolute index in the original substring `orig` (absolute positions start at pathStart).
// The normalization removes whitespace/newlines around dots; this function
// advances a normalized counter when it encounters identifier chars or dots
// and returns the corresponding original index (absolute).
private int mapNormalizedOffsetToOriginal(String orig, int normalizedOffset, int pathStart) {
if (orig == null || orig.isEmpty()) return pathStart;
int norm = 0;
for (int i = 0; i < orig.length(); i++) {
char c = orig.charAt(i);
if (Character.isLetterOrDigit(c) || c == '_' || c == '.') {
norm++;
}
if (norm >= normalizedOffset) {
return pathStart + i;
}
}
return pathStart + orig.length();
}
public void init(String text,int width, int height) {
this.text = text == null ? "" : text.replaceAll("\\r?\\n|\\r", "\n");
lines.clear();
@@ -519,75 +541,94 @@ public class JavaTextContainer extends TextContainer {
// Use ClassPathFinder to resolve the import
ClassPathFinder.ResolveResult result = classPathFinder.resolve(fullPath);
String packagePortion = result.packagePortion;
String classPortion = result.classPortion;
boolean hasPackage = packagePortion != null && !packagePortion.isEmpty();
boolean hasClass = classPortion != null && !classPortion.isEmpty();
// Tokenize the original matched substring so we can map normalized segments
String orig = text.substring(pathStart, pathEnd);
java.util.List<String> tokens = new java.util.ArrayList<>();
java.util.List<Integer> tokenStarts = new java.util.ArrayList<>();
java.util.List<Integer> tokenEnds = new java.util.ArrayList<>();
Matcher idm = Pattern.compile("[A-Za-z_][A-Za-z0-9_]*").matcher(orig);
while (idm.find()) {
tokens.add(idm.group());
tokenStarts.add(idm.start());
tokenEnds.add(idm.end());
}
int pkgSegments = result.packageSegmentCount;
boolean hasPackage = pkgSegments > 0;
// Highlight package tokens (if any)
if (result.found && result.invalidStartOffset < 0) {
// Fully resolved - highlight package (blue) and class segments (typed)
if (hasPackage) {
int pkgEnd = pathStart + packagePortion.length();
if (hasPackage && pkgSegments <= tokenEnds.size()) {
int pkgEnd = pathStart + tokenEnds.get(pkgSegments - 1);
marks.add(new Mark(pathStart, pkgEnd, TokenType.TYPE_DECL));
}
if (hasClass && !result.classSegments.isEmpty()) {
int classStart = pathStart + (hasPackage ? packagePortion.length() + 1 : 0);
for (ClassPathFinder.ClassSegment seg : result.classSegments) {
int segEnd = classStart + seg.name.length();
// Highlight class segments
if (result.classSegments != null && !result.classSegments.isEmpty()) {
int classTokenStart = hasPackage ? pkgSegments : 0;
for (int si = 0; si < result.classSegments.size(); si++) {
int tokIdx = classTokenStart + si;
if (tokIdx >= 0 && tokIdx < tokenStarts.size()) {
int s = pathStart + tokenStarts.get(tokIdx);
int e = pathStart + tokenEnds.get(tokIdx);
ClassPathFinder.ClassSegment seg = result.classSegments.get(si);
TokenType segType;
switch (seg.type) {
case INTERFACE: segType = TokenType.INTERFACE_DECL; break;
case ENUM: segType = TokenType.ENUM_DECL; break;
default: segType = TokenType.IMPORTED_CLASS; break;
}
marks.add(new Mark(s, e, segType));
}
}
}
} else if (result.classSegments != null && !result.classSegments.isEmpty()) {
// Partial resolution - mark package and valid class segments
if (hasPackage && pkgSegments <= tokenEnds.size()) {
int pkgEnd = pathStart + tokenEnds.get(pkgSegments - 1);
marks.add(new Mark(pathStart, pkgEnd, TokenType.TYPE_DECL));
}
int classTokenStart = hasPackage ? pkgSegments : 0;
for (int si = 0; si < result.classSegments.size(); si++) {
int tokIdx = classTokenStart + si;
if (tokIdx >= 0 && tokIdx < tokenStarts.size()) {
int s = pathStart + tokenStarts.get(tokIdx);
int e = pathStart + tokenEnds.get(tokIdx);
ClassPathFinder.ClassSegment seg = result.classSegments.get(si);
TokenType segType;
switch (seg.type) {
case INTERFACE: segType = TokenType.INTERFACE_DECL; break;
case ENUM: segType = TokenType.ENUM_DECL; break;
default: segType = TokenType.IMPORTED_CLASS; break;
}
marks.add(new Mark(classStart, segEnd, segType));
classStart = segEnd + 1;
marks.add(new Mark(s, e, segType));
}
}
} else if (result.classSegments != null && !result.classSegments.isEmpty()) {
// Partial resolution - some class segments are valid, rest is invalid
// (e.g., IOverlay.ColorType resolved but ".idk" is invalid)
if (hasPackage) {
int pkgEnd = pathStart + packagePortion.length();
marks.add(new Mark(pathStart, pkgEnd, TokenType.TYPE_DECL));
}
int classStart = pathStart + (hasPackage ? packagePortion.length() + 1 : 0);
for (ClassPathFinder.ClassSegment seg : result.classSegments) {
int segEnd = classStart + seg.name.length();
TokenType segType;
switch (seg.type) {
case INTERFACE: segType = TokenType.INTERFACE_DECL; break;
case ENUM: segType = TokenType.ENUM_DECL; break;
default: segType = TokenType.IMPORTED_CLASS; break;
}
marks.add(new Mark(classStart, segEnd, segType));
classStart = segEnd + 1;
}
// Mark remaining invalid portion in red
// Mark remaining invalid portion in red (map normalized offset to original)
if (result.invalidStartOffset >= 0) {
int invalidStart = pathStart + result.invalidStartOffset;
if (invalidStart < pathEnd) {
marks.add(new Mark(invalidStart, pathEnd, TokenType.UNDEFINED_VAR));
int invalidAbs = mapNormalizedOffsetToOriginal(orig, result.invalidStartOffset, pathStart);
if (invalidAbs < pathEnd) {
marks.add(new Mark(invalidAbs, pathEnd, TokenType.UNDEFINED_VAR));
}
}
} else {
// Not resolved at all - use invalidStartOffset to determine valid/invalid split
if (result.invalidStartOffset >= 0 && hasPackage) {
// Valid package prefix, invalid class
int pkgEnd = pathStart + packagePortion.length();
marks.add(new Mark(pathStart, pkgEnd, TokenType.TYPE_DECL));
int invalidStart = pathStart + result.invalidStartOffset;
if (invalidStart < pathEnd) {
marks.add(new Mark(invalidStart, pathEnd, TokenType.UNDEFINED_VAR));
if (pkgSegments <= tokenEnds.size()) {
int pkgEnd = pathStart + tokenEnds.get(pkgSegments - 1);
marks.add(new Mark(pathStart, pkgEnd, TokenType.TYPE_DECL));
}
} else if (fullPath.endsWith(".") && hasPackage && result.invalidStartOffset < 0 && !hasClass) {
int invalidAbs = mapNormalizedOffsetToOriginal(orig, result.invalidStartOffset, pathStart);
if (invalidAbs < pathEnd) {
marks.add(new Mark(invalidAbs, pathEnd, TokenType.UNDEFINED_VAR));
}
} else if (fullPath.endsWith(".") && hasPackage && result.invalidStartOffset < 0) {
// Trailing dot on a fully-valid package - all blue
marks.add(new Mark(pathStart, pathEnd, TokenType.TYPE_DECL));
} else if (result.invalidStartOffset < 0 && hasPackage && !hasClass) {
} else if (result.invalidStartOffset < 0 && hasPackage) {
// All-lowercase path treated as valid package
marks.add(new Mark(pathStart, pathEnd, TokenType.TYPE_DECL));
} else {