All new FormatHelper;

* Built in FormatSettings
 * takes care of all appropriate formatting operations needed in this editor's context
This commit is contained in:
bigguy345 committed 2025-12-21 00:49:02 +02:00
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() {