mirror of
https://github.com/KAMKEEL/CustomNPC-Plus.git
synced 2026-11-12 21:17:32 +00:00
More formatting and syntax highlight fixes
This commit is contained in:
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;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user