mirror of
https://github.com/KAMKEEL/CustomNPC-Plus.git
synced 2026-11-12 20:07:32 +00:00
All new FormatHelper;
* Built in FormatSettings * takes care of all appropriate formatting operations needed in this editor's context
This commit is contained in:
1 parent
918c3acf7f
commit
6f0ccace42
4 files changed
+1303
-27
No files matched your search
File diff suppressed because it is too large.
Load diff
@@ -145,36 +145,86 @@ public class IndentHelper {
|
||||
}
|
||||
|
||||
/**
|
||||
* Format entire text with proper indentation
|
||||
* Format entire text with proper indentation and operator spacing
|
||||
*/
|
||||
public static FormatResult formatText(String text, int cursorPosition) {
|
||||
String[] linesArr = text.split("\n", -1);
|
||||
StringBuilder out = new StringBuilder(text.length() + 32);
|
||||
return formatText(text, cursorPosition, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Format entire text with proper indentation, operator spacing, and optional line wrapping
|
||||
* @param text The text to format
|
||||
* @param cursorPosition Current cursor position
|
||||
* @param viewportWidth Viewport width for line wrapping (0 = no wrapping)
|
||||
*/
|
||||
public static FormatResult formatText(String text, int cursorPosition, int viewportWidth) {
|
||||
// First pass: fix indentation
|
||||
String indented = formatIndentation(text, cursorPosition);
|
||||
|
||||
// Second pass: apply operator spacing and whitespace normalization
|
||||
FormatHelper helper = new FormatHelper();
|
||||
FormatHelper.FormatSettings settings = helper.getSettings();
|
||||
|
||||
// Enable wrapping if viewport width is provided
|
||||
if (viewportWidth > 0) {
|
||||
settings.wrapLongLines = true;
|
||||
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));
|
||||
}
|
||||
|
||||
String formatted = helper.format(indented);
|
||||
|
||||
// Apply wrapping if enabled
|
||||
if (viewportWidth > 0 && settings.wrapLongLines) {
|
||||
formatted = helper.wrapLines(formatted, settings.maxLineLength);
|
||||
}
|
||||
|
||||
// Recalculate cursor position (try to maintain relative position)
|
||||
int newCursorPos = Math.min(cursorPosition, formatted.length());
|
||||
|
||||
// Try to find cursor on same line with same content context
|
||||
String[] origLines = text.split("\n", -1);
|
||||
String[] newLines = formatted.split("\n", -1);
|
||||
|
||||
// Track cursor position in formatted output
|
||||
int cursorLine = -1;
|
||||
int cursorColInContent = 0;
|
||||
int lineStartPos = 0;
|
||||
int cursorLine = -1;
|
||||
int cursorColInLine = 0;
|
||||
|
||||
// Find cursor's line and column
|
||||
for (int li = 0; li < linesArr.length; li++) {
|
||||
String line = linesArr[li];
|
||||
int lineEndPos = lineStartPos + line.length();
|
||||
|
||||
for (int li = 0; li < origLines.length; li++) {
|
||||
int lineEndPos = lineStartPos + origLines[li].length();
|
||||
if (cursorPosition >= lineStartPos && cursorPosition <= lineEndPos) {
|
||||
cursorLine = li;
|
||||
int leadingSpaces = line.length() - line.replaceAll("^[ \\t]+", "").length();
|
||||
cursorColInContent = Math.max(0, cursorPosition - lineStartPos - leadingSpaces);
|
||||
cursorColInLine = cursorPosition - lineStartPos;
|
||||
break;
|
||||
}
|
||||
lineStartPos = lineEndPos + 1;
|
||||
}
|
||||
|
||||
if (cursorLine >= 0 && cursorLine < newLines.length) {
|
||||
int newLineStart = 0;
|
||||
for (int li = 0; li < cursorLine; li++) {
|
||||
newLineStart += newLines[li].length() + 1;
|
||||
}
|
||||
// Clamp cursor to new line length
|
||||
newCursorPos = newLineStart + Math.min(cursorColInLine, newLines[cursorLine].length());
|
||||
}
|
||||
|
||||
return new FormatResult(formatted, newCursorPos);
|
||||
}
|
||||
|
||||
/**
|
||||
* Format indentation only (first pass)
|
||||
*/
|
||||
private static String formatIndentation(String text, int cursorPosition) {
|
||||
String[] linesArr = text.split("\n", -1);
|
||||
StringBuilder out = new StringBuilder(text.length() + 32);
|
||||
|
||||
boolean inString = false;
|
||||
boolean escape = false;
|
||||
boolean inBlockComment = false;
|
||||
int depth = 0;
|
||||
int newCursorPos = 0;
|
||||
|
||||
for (int li = 0; li < linesArr.length; li++) {
|
||||
String line = linesArr[li];
|
||||
@@ -218,17 +268,13 @@ public class IndentHelper {
|
||||
if (startsWithClose) indentLevel = Math.max(0, indentLevel - 1);
|
||||
int targetIndent = indentLevel * TAB_SIZE;
|
||||
|
||||
if (li == cursorLine) {
|
||||
newCursorPos = out.length() + targetIndent + Math.min(cursorColInContent, trimmedLeading.length());
|
||||
}
|
||||
|
||||
out.append(spaces(targetIndent)).append(trimmedLeading);
|
||||
if (li < linesArr.length - 1) out.append('\n');
|
||||
|
||||
depth = Math.max(0, depth + opens - closes);
|
||||
}
|
||||
|
||||
return new FormatResult(out.toString(), newCursorPos);
|
||||
return out.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -139,22 +139,95 @@ public class JavaTextContainer extends TextContainer {
|
||||
}
|
||||
|
||||
private void highlightVariableReferences(List<Mark> marks) {
|
||||
// Known Java keywords and types that shouldn't be flagged as undefined
|
||||
java.util.Set<String> knownIdentifiers = new java.util.HashSet<>(java.util.Arrays.asList(
|
||||
// Primitive types
|
||||
"boolean", "int", "float", "double", "long", "char", "byte", "short", "void",
|
||||
// Keywords
|
||||
"null", "true", "false", "if", "else", "switch", "case", "for", "while", "do",
|
||||
"try", "catch", "finally", "return", "throw", "var", "let", "const", "function",
|
||||
"continue", "break", "this", "new", "typeof", "instanceof", "class", "interface",
|
||||
"extends", "implements", "import", "package", "public", "private", "protected",
|
||||
"static", "final", "abstract", "synchronized", "native", "default", "enum",
|
||||
"throws", "super", "assert", "volatile", "transient", "strictfp", "goto",
|
||||
// Common JS/scripting keywords
|
||||
"undefined", "NaN", "Infinity", "arguments", "prototype", "constructor",
|
||||
// Common types (first letter uppercase pattern handles most)
|
||||
"String", "Object", "Array", "Math", "System", "Integer", "Double", "Float",
|
||||
"Boolean", "Long", "Byte", "Short", "Character", "List", "Map", "Set"
|
||||
));
|
||||
|
||||
Pattern identifier = Pattern.compile("\\b([a-zA-Z_][a-zA-Z0-9_]*)\\b");
|
||||
Matcher m = identifier.matcher(text);
|
||||
while (m.find()) {
|
||||
String name = m.group(1);
|
||||
int position = m.start(1);
|
||||
|
||||
// Check if this variable is local to the method it's in
|
||||
|
||||
// Skip known keywords and types
|
||||
if (knownIdentifiers.contains(name)) continue;
|
||||
|
||||
// Skip type names (first letter uppercase)
|
||||
if (Character.isUpperCase(name.charAt(0))) continue;
|
||||
|
||||
// Check if inside a method
|
||||
MethodBlock methodBlock = findMethodBlockAtPosition(position);
|
||||
if (methodBlock != null && methodBlock.localVariables.contains(name)) {
|
||||
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));
|
||||
|
||||
if (methodBlock != null) {
|
||||
// Inside a method - check in order: parameter, local, global
|
||||
if (methodBlock.parameters.contains(name)) {
|
||||
marks.add(new Mark(m.start(1), m.end(1), TokenType.PARAMETER));
|
||||
} else if (methodBlock.localVariables.contains(name)) {
|
||||
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));
|
||||
} else {
|
||||
// Unknown variable - mark as undefined (Bug 11)
|
||||
// But only if it looks like a variable reference (not a method call)
|
||||
if (!isMethodCall(position) && !isTypeReference(name, position)) {
|
||||
marks.add(new Mark(m.start(1), m.end(1), TokenType.UNDEFINED_VAR));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Outside any method - check global fields
|
||||
if (globalFields.contains(name)) {
|
||||
marks.add(new Mark(m.start(1), m.end(1), TokenType.GLOBAL_FIELD));
|
||||
}
|
||||
// Don't mark as undefined outside methods - could be a type name or declaration
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the identifier at this position is a method call (followed by parenthesis)
|
||||
*/
|
||||
private boolean isMethodCall(int position) {
|
||||
// Skip whitespace after identifier
|
||||
int i = position;
|
||||
while (i < text.length() && Character.isLetterOrDigit(text.charAt(i)) || text.charAt(i) == '_') {
|
||||
i++;
|
||||
}
|
||||
// Skip whitespace
|
||||
while (i < text.length() && Character.isWhitespace(text.charAt(i))) {
|
||||
i++;
|
||||
}
|
||||
// Check for opening paren
|
||||
return i < text.length() && text.charAt(i) == '(';
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this looks like a type reference (e.g., part of a declaration or generic)
|
||||
*/
|
||||
private boolean isTypeReference(String name, int position) {
|
||||
// Check if preceded by 'new ', '<', or ',<space>Type'
|
||||
if (position > 4) {
|
||||
String before = text.substring(Math.max(0, position - 5), position);
|
||||
if (before.endsWith("new ") || before.endsWith("< ") || before.endsWith("<")) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public void formatCodeText() {
|
||||
// Step 1: Tokenize the full text
|
||||
List<Mark> marks = new ArrayList<>();
|
||||
@@ -366,8 +439,10 @@ public class JavaTextContainer extends TextContainer {
|
||||
METHOD_CALL('a', 50),
|
||||
NUMBER('7', 40),
|
||||
VARIABLE('f', 30),
|
||||
GLOBAL_FIELD('b', 35), // new
|
||||
LOCAL_FIELD('e', 25), // new
|
||||
GLOBAL_FIELD('b', 35), // aqua - class-level fields
|
||||
LOCAL_FIELD('e', 25), // yellow - local variables
|
||||
PARAMETER('9', 36), // blue - method parameters (Bug 10)
|
||||
UNDEFINED_VAR('4', 10), // dark red - undefined variables (Bug 11)
|
||||
DEFAULT('f', 0);
|
||||
|
||||
public final char color;
|
||||
|
||||
@@ -16,18 +16,49 @@ public class MethodBlock {
|
||||
// Pattern for variable declarations (same as in JavaTextContainer)
|
||||
public static final Pattern LOCAL_VAR_DECL = Pattern.compile(
|
||||
"\\b([A-Z][a-zA-Z0-9_<>\\[\\]]*|[a-z][a-zA-Z0-9_]*)\\s+([a-zA-Z_][a-zA-Z0-9_]*)\\s*(=|;)");
|
||||
|
||||
// Pattern for method parameters: Type name (with optional ... for varargs)
|
||||
public static final Pattern PARAMETER_PATTERN = Pattern.compile(
|
||||
"([a-zA-Z_][a-zA-Z0-9_<>\\[\\]]*(?:\\.{3})?)\\s+([a-zA-Z_][a-zA-Z0-9_]*)");
|
||||
|
||||
public int startOffset;
|
||||
public int endOffset;
|
||||
public String text;
|
||||
public List<String> localVariables = new ArrayList<>();
|
||||
public List<String> parameters = new ArrayList<>(); // Method parameters
|
||||
|
||||
public MethodBlock(int start, int end, String text) {
|
||||
this.startOffset = start;
|
||||
this.endOffset = end;
|
||||
this.text = text;
|
||||
extractParameters();
|
||||
extractLocalVariables();
|
||||
}
|
||||
|
||||
// Extract method parameters from the method signature
|
||||
private void extractParameters() {
|
||||
parameters.clear();
|
||||
|
||||
// Find the parameter list between ( and )
|
||||
int parenStart = text.indexOf('(');
|
||||
int parenEnd = text.indexOf(')');
|
||||
|
||||
if (parenStart < 0 || parenEnd <= parenStart) return;
|
||||
|
||||
String paramList = text.substring(parenStart + 1, parenEnd);
|
||||
|
||||
// Skip if empty
|
||||
if (paramList.trim().isEmpty()) return;
|
||||
|
||||
// Split by comma (but be careful of generic types like List<A, B>)
|
||||
Matcher m = PARAMETER_PATTERN.matcher(paramList);
|
||||
while (m.find()) {
|
||||
String paramName = m.group(2);
|
||||
if (!parameters.contains(paramName)) {
|
||||
parameters.add(paramName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Extract local variables declared within this method
|
||||
private void extractLocalVariables() {
|
||||
|
||||
Reference in new issue
Block a user