More formatting and syntax highlight fixes

This commit is contained in:
bigguy345 committed 2025-12-21 16:27:22 +02:00
1 parent b3a64f92d7
commit a05d411cba
3 files changed
+261 -4

No files matched your search

@@ -142,6 +142,131 @@ public class FormatHelper {
return result.toString();
}
/**
* Split multiple statements on one line into separate lines with proper indentation.
* E.g., " this.a = 1; this.b = 2;" becomes two lines
* Also handles scope: "if(x){ int y = 20; }" becomes proper multi-line structure
*/
private String splitStatementsOntoSeparateLines(String text) {
String[] lines = text.split("\n", -1);
StringBuilder result = new StringBuilder();
for (int lineIdx = 0; lineIdx < lines.length; lineIdx++) {
String line = lines[lineIdx];
// Skip empty lines or comment-only lines
String trimmed = line.trim();
if (trimmed.isEmpty() || trimmed.startsWith("//") || trimmed.startsWith("/*") || trimmed.startsWith("*")) {
result.append(line);
if (lineIdx < lines.length - 1) result.append("\n");
continue;
}
// Extract leading indentation
int indent = 0;
while (indent < line.length() && (line.charAt(indent) == ' ' || line.charAt(indent) == '\t')) {
indent++;
}
String indentation = line.substring(0, indent);
String content = line.substring(indent);
// Split by semicolons (but preserve those in strings, for loops, etc.)
List<String> statements = splitBySemicolons(content);
for (int si = 0; si < statements.size(); si++) {
String stmt = statements.get(si).trim();
if (stmt.isEmpty()) continue;
// Check if statement opens a scope { and has code after it
int braceIdx = stmt.indexOf('{');
if (braceIdx >= 0 && braceIdx < stmt.length() - 1) {
String afterBrace = stmt.substring(braceIdx + 1).trim();
if (!afterBrace.isEmpty() && !afterBrace.equals("}")) {
// Split: keep up to and including {, put rest on new line with extra indent
result.append(indentation).append(stmt, 0, braceIdx + 1).append("\n");
// Calculate new indent (add 4 spaces)
String newIndent = indentation + " ";
result.append(newIndent).append(afterBrace);
if (si < statements.size() - 1) result.append("\n");
continue;
}
}
result.append(indentation).append(stmt);
// Add semicolon back if it was removed
if (si < statements.size() - 1) {
result.append("\n");
}
}
if (lineIdx < lines.length - 1) result.append("\n");
}
return result.toString();
}
/**
* Split content by semicolons, preserving those in strings and for-loops
*/
private List<String> splitBySemicolons(String content) {
List<String> statements = new ArrayList<>();
StringBuilder current = new StringBuilder();
int parenDepth = 0;
boolean inString = false;
boolean inChar = false;
boolean escaped = false;
int forLoopDepth = 0; // Track for(...) depth to preserve semicolons
for (int i = 0; i < content.length(); i++) {
char c = content.charAt(i);
char prev = i > 0 ? content.charAt(i - 1) : ' ';
// Track strings
if (c == '\"' && !escaped && !inChar) {
inString = !inString;
} else if (c == '\'' && !escaped && !inString) {
inChar = !inChar;
}
escaped = (c == '\\' && !escaped);
if (inString || inChar) {
current.append(c);
continue;
}
// Track for loops
if (c == '(' && i >= 3 && content.substring(Math.max(0, i - 3), i + 1).matches(".*\\bfor\\s*\\(")) {
forLoopDepth++;
parenDepth++;
} else if (c == '(') {
parenDepth++;
} else if (c == ')') {
parenDepth--;
if (forLoopDepth > 0 && parenDepth < forLoopDepth) {
forLoopDepth = 0;
}
}
// Semicolon: split if not in for-loop header
if (c == ';' && forLoopDepth == 0) {
current.append(c);
statements.add(current.toString());
current = new StringBuilder();
continue;
}
current.append(c);
}
// Add remaining content
if (current.length() > 0) {
statements.add(current.toString());
}
return statements;
}
private int getLineStartOffset(String[] lines, int lineIndex) {
int offset = 0;
for (int i = 0; i < lineIndex; i++) {
@@ -172,12 +297,39 @@ public class FormatHelper {
return line;
}
// Skip formatting for continuation lines (lines that start with operators, dots, etc.)
// These are wrapped lines from a previous format and should be preserved as-is
if (isContinuationLine(trimmed)) {
return line;
}
// Format the content
String formatted = formatContent(content, fullText, lineOffset + indent);
return indentation + formatted;
}
/**
* Check if a line appears to be a continuation of a previous line
* (i.e., starts with an operator, dot, or other continuation character)
*/
private boolean isContinuationLine(String trimmedContent) {
if (trimmedContent.isEmpty()) return false;
char firstChar = trimmedContent.charAt(0);
// Lines starting with these are likely continuations
if (firstChar == '.' || firstChar == '+' || firstChar == '-' ||
firstChar == '*' || firstChar == '/' || firstChar == '%' ||
firstChar == '&' || firstChar == '|' || firstChar == '^' ||
firstChar == '?' || firstChar == ':' || firstChar == ',') {
return true;
}
// Also check for && and ||
if (trimmedContent.startsWith("&&") || trimmedContent.startsWith("||")) {
return true;
}
return false;
}
/**
* Format line content, preserving strings and comments
*/
@@ -229,6 +229,7 @@ public class IndentHelper {
for (int li = 0; li < linesArr.length; li++) {
String line = linesArr[li];
String trimmedLeading = line.replaceAll("^[ \\t]+", "");
int originalIndent = getLineIndent(line);
int opens = 0, closes = 0;
boolean startsWithClose = false;
@@ -268,7 +269,23 @@ public class IndentHelper {
if (startsWithClose) indentLevel = Math.max(0, indentLevel - 1);
int targetIndent = indentLevel * TAB_SIZE;
out.append(spaces(targetIndent)).append(trimmedLeading);
// Check if this is a continuation line:
// 1. Line starts with operator/dot (explicit continuation)
// 2. Previous line ends with operator (implicit continuation - this line continues it)
// If so, preserve extra indentation beyond the base level
boolean isContinuation = isContinuationLine(trimmedLeading);
if (!isContinuation && li > 0) {
// Check if previous line ends with an operator (indicating this line continues it)
String prevLine = linesArr[li - 1].trim();
isContinuation = lineEndsWithContinuationOperator(prevLine);
}
if (isContinuation && originalIndent > targetIndent) {
// This is a continuation line - preserve its extra indent
out.append(spaces(originalIndent)).append(trimmedLeading);
} else {
out.append(spaces(targetIndent)).append(trimmedLeading);
}
if (li < linesArr.length - 1) out.append('\n');
depth = Math.max(0, depth + opens - closes);
@@ -277,6 +294,56 @@ public class IndentHelper {
return out.toString();
}
/**
* Check if a line appears to be a continuation of a previous line
* (i.e., starts with an operator, dot, or other continuation character)
*/
private static boolean isContinuationLine(String trimmedContent) {
if (trimmedContent.isEmpty()) return false;
char firstChar = trimmedContent.charAt(0);
// Lines starting with these are likely continuations
if (firstChar == '.' || firstChar == '+' || firstChar == '-' ||
firstChar == '*' || firstChar == '/' || firstChar == '%' ||
firstChar == '&' || firstChar == '|' || firstChar == '^' ||
firstChar == '?' || firstChar == ':' || firstChar == ',') {
return true;
}
// Also check for && and ||
if (trimmedContent.startsWith("&&") || trimmedContent.startsWith("||")) {
return true;
}
return false;
}
/**
* Check if a line ends with an operator that indicates the next line is a continuation
*/
private static boolean lineEndsWithContinuationOperator(String trimmedLine) {
if (trimmedLine.isEmpty()) return false;
// Remove trailing comments
int commentIdx = trimmedLine.indexOf("//");
if (commentIdx >= 0) {
trimmedLine = trimmedLine.substring(0, commentIdx).trim();
}
if (trimmedLine.isEmpty()) return false;
// Check if ends with operator
char lastChar = trimmedLine.charAt(trimmedLine.length() - 1);
if (lastChar == '+' || lastChar == '-' || lastChar == '*' ||
lastChar == '/' || lastChar == '%' || lastChar == '=' ||
lastChar == '&' || lastChar == '|' || lastChar == '^' ||
lastChar == '?' || lastChar == ':' || lastChar == ',' ||
lastChar == '(' || lastChar == '[') {
// But not if it's ; or } which end statements
return true;
}
// Check for && and ||
if (trimmedLine.endsWith("&&") || trimmedLine.endsWith("||")) {
return true;
}
return false;
}
/**
* Result of text formatting
*/
@@ -16,7 +16,7 @@ public class JavaTextContainer extends TextContainer {
"\\b(null|boolean|int|float|double|long|char|byte|short|void|if|else|switch|case|for|while|do|try|catch|finally|return|throw|var|let|const|function|continue|break|this|new|typeof|instanceof)\\b");
public static final Pattern TYPE_DECL = Pattern.compile("\\b([A-Za-z_][a-zA-Z0-9_]*)" + // Group 1 → main type
"\\s*(<([^>]+)>)?" + // Group c2 → <…>, Group 3 → inner type
"\\s*(<([^>]+)>)?" + // Group 2 → <…>, Group 3 → inner type
"\\s+[a-zA-Z_][a-zA-Z0-9_]*" // variable name
);
@@ -275,12 +275,50 @@ public class JavaTextContainer extends TextContainer {
/**
* Check if this looks like a type reference (e.g., part of a declaration or generic)
* But NOT a comparison like "i < container" which uses < as less-than operator
*/
private boolean isTypeReference(String name, int position) {
// Check if preceded by 'new ', '<', or ',<space>Type'
// Check if preceded by 'new '
if (position > 4) {
String before = text.substring(Math.max(0, position - 5), position);
if (before.endsWith("new ") || before.endsWith("< ") || before.endsWith("<")) {
if (before.endsWith("new ")) {
return true;
}
}
// Check if preceded by < but make sure it's a generic, not a comparison
// Generic: Type<Name or ,Name in generics
// Comparison: value < name (space before < means comparison)
if (position > 1) {
int checkPos = position - 1;
// Skip whitespace
while (checkPos > 0 && Character.isWhitespace(text.charAt(checkPos))) {
checkPos--;
}
if (checkPos >= 0 && text.charAt(checkPos) == '<') {
// Check what's before the <
int beforeLt = checkPos - 1;
while (beforeLt >= 0 && Character.isWhitespace(text.charAt(beforeLt))) {
beforeLt--;
}
if (beforeLt >= 0) {
char beforeChar = text.charAt(beforeLt);
// If it's a letter/digit/underscore (identifier) or ) followed by space + <, it's a comparison
// If it's a type name directly followed by <, it's a generic
// Check if there was whitespace between the identifier and <
boolean hasSpaceBeforeLt = (checkPos > 0 && Character.isWhitespace(text.charAt(checkPos - 1)));
if (hasSpaceBeforeLt && (Character.isLetterOrDigit(beforeChar) || beforeChar == '_' || beforeChar == ')')) {
// This is a comparison like "i < container" or "(x + 1) < y"
return false;
}
// Otherwise it's likely a generic like List<String>
if (Character.isLetter(beforeChar) || beforeChar == '>') {
return true;
}
}
}
// Check for comma in generics like Map<K, V>
if (checkPos >= 0 && text.charAt(checkPos) == ',') {
return true;
}
}