From a05d411cbadc7a678b898dbe2dfc4413c8f2004b Mon Sep 17 00:00:00 2001 From: bigguy345 Date: Sun, 21 Dec 2025 16:27:22 +0200 Subject: [PATCH] More formatting and syntax highlight fixes --- .../client/gui/util/script/FormatHelper.java | 152 ++++++++++++++++++ .../client/gui/util/script/IndentHelper.java | 69 +++++++- .../gui/util/script/JavaTextContainer.java | 44 ++++- 3 files changed, 261 insertions(+), 4 deletions(-) diff --git a/src/main/java/noppes/npcs/client/gui/util/script/FormatHelper.java b/src/main/java/noppes/npcs/client/gui/util/script/FormatHelper.java index 675a6b097..5c534cc94 100644 --- a/src/main/java/noppes/npcs/client/gui/util/script/FormatHelper.java +++ b/src/main/java/noppes/npcs/client/gui/util/script/FormatHelper.java @@ -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 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 splitBySemicolons(String content) { + List 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 */ diff --git a/src/main/java/noppes/npcs/client/gui/util/script/IndentHelper.java b/src/main/java/noppes/npcs/client/gui/util/script/IndentHelper.java index 88bc354b8..fd6da00f0 100644 --- a/src/main/java/noppes/npcs/client/gui/util/script/IndentHelper.java +++ b/src/main/java/noppes/npcs/client/gui/util/script/IndentHelper.java @@ -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 */ diff --git a/src/main/java/noppes/npcs/client/gui/util/script/JavaTextContainer.java b/src/main/java/noppes/npcs/client/gui/util/script/JavaTextContainer.java index 391afa5f7..fa6320470 100644 --- a/src/main/java/noppes/npcs/client/gui/util/script/JavaTextContainer.java +++ b/src/main/java/noppes/npcs/client/gui/util/script/JavaTextContainer.java @@ -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 ',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 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 + if (Character.isLetter(beforeChar) || beforeChar == '>') { + return true; + } + } + } + // Check for comma in generics like Map + if (checkPos >= 0 && text.charAt(checkPos) == ',') { return true; } }