Merge remote-tracking branch 'origin/janino-scripts' into janino-scripts

# Conflicts:
#	src/main/java/noppes/npcs/client/gui/util/GuiScriptTextArea.java
This commit is contained in:
bigguy345 committed 2025-12-18 01:09:21 +02:00
commit 8b232bec4f
2 files changed
+206 -8

No files matched your search

@@ -17,7 +17,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 2 → <…>, Group 3 → inner type
"\\s*(<([^>]+)>)?" + // Group c2 → <…>, Group 3 → inner type
"\\s+[a-zA-Z_][a-zA-Z0-9_]*" // variable name
);
@@ -47,12 +47,102 @@ public class JavaTextContainer extends TextContainer {
public int lineHeight;
public int totalHeight;
public int visibleLines = 1;
// Cached brace spans: list of {level, startLine, endLine}
private List<int[]> braceSpansCache = null;
public JavaTextContainer(String text) {
super(text);
this.text = text.replaceAll("\\r?\\n|\\r", "\n");
}
public synchronized List<int[]> getBraceSpans() {
if (braceSpansCache != null)
return braceSpansCache;
List<int[]> spans = new ArrayList<>();
if (text == null || text.isEmpty() || lines == null || lines.isEmpty()) {
braceSpansCache = spans;
return spans;
}
// Heuristics limits to avoid runaway spans when braces are unbalanced.
final int MAX_SPAN_LINES = 200; // cap span length to this many lines
final int MAX_STACK_DEPTH = 512; // avoid excessive stack growth
List<int[]> excluded = MethodBlock.getExcludedRanges(text);
int exIdx = 0;
java.util.ArrayList<Integer> openStack = new java.util.ArrayList<>();
int len = text.length();
for (int p = 0; p < len; p++) {
while (exIdx < excluded.size() && p >= excluded.get(exIdx)[1])
exIdx++;
if (exIdx < excluded.size()) {
int[] r = excluded.get(exIdx);
if (p >= r[0] && p < r[1]) {
p = r[1] - 1;
continue;
}
}
char c = text.charAt(p);
if (c == '{') {
if (openStack.size() < MAX_STACK_DEPTH) {
// Look ahead to see if this open has a plausible closing brace within limits.
int openLine = getLineIndex(p);
int lookaheadChars = Math.max(1000, MAX_SPAN_LINES * 120);
int searchEnd = Math.min(len, p + lookaheadChars);
boolean foundClose = false;
int exIdx2 = exIdx; // local index for scanning ahead
for (int q = p + 1; q < searchEnd; q++) {
while (exIdx2 < excluded.size() && q >= excluded.get(exIdx2)[1]) exIdx2++;
if (exIdx2 < excluded.size()) {
int[] r2 = excluded.get(exIdx2);
if (q >= r2[0] && q < r2[1]) { q = r2[1] - 1; continue; }
}
if (text.charAt(q) == '}') {
int closeLineCandidate = getLineIndex(q);
if (closeLineCandidate - openLine <= MAX_SPAN_LINES) {
foundClose = true;
break;
}
}
}
if (foundClose) openStack.add(p);
}
} else if (c == '}') {
if (!openStack.isEmpty()) {
int openPos = openStack.remove(openStack.size() - 1);
int level = openStack.size() + 1;
int openLine = getLineIndex(openPos);
int closeLine = getLineIndex(p);
// Cap spans to avoid drawing across the whole document when syntax is broken
if (closeLine - openLine > MAX_SPAN_LINES) {
closeLine = Math.min(openLine + MAX_SPAN_LINES, lines.size() - 1);
}
// Only add spans where closeLine > openLine (multi-line blocks)
if (closeLine > openLine) {
spans.add(new int[]{level, openLine, closeLine});
}
}
}
}
braceSpansCache = spans;
return spans;
}
private int getLineIndex(int pos) {
if (lines == null || lines.isEmpty())
return 0;
int length = text == null ? 0 : text.length();
for (int i = 0; i < lines.size(); i++) {
LineData d = lines.get(i);
int s = Math.max(0, Math.min(d.start, length));
int e = Math.max(0, Math.min(d.end, length));
if (pos >= s && pos <= e)
return i;
}
return Math.max(0, lines.size() - 1);
}
public void init(int width, int height) {
lineHeight = ClientProxy.Font.height();
if (lineHeight == 0)
@@ -81,6 +171,9 @@ public class JavaTextContainer extends TextContainer {
linesCount = lines.size();
totalHeight = linesCount * lineHeight;
visibleLines = Math.max(height / lineHeight, 1);
// Invalidate caches that depend on line layout
invalidateCaches();
}
private List<String> globalFields = new ArrayList<>();
@@ -153,6 +246,9 @@ public class JavaTextContainer extends TextContainer {
}
}
public synchronized void invalidateCaches() {
this.braceSpansCache = null;
}
public void formatCodeText() {
// Step 1: Tokenize the full text
List<Mark> marks = new ArrayList<>();
@@ -175,6 +271,14 @@ public class JavaTextContainer extends TextContainer {
marks = resolveConflicts(marks);
// Compute indent guides based on matched braces, ignoring strings/comments
computeIndentGuides(marks);
// Invalidate cached brace spans; will be recomputed on demand
invalidateCaches();
// Step 2: Clear existing tokens
for (LineData line : lines) {
line.tokens.clear();
@@ -285,6 +389,7 @@ public class JavaTextContainer extends TextContainer {
public String text;
public int start, end;
public List<Token> tokens = new ArrayList<>();
public List<Integer> indentCols = new ArrayList<>();
public LineData(String text, int startIndex, int end) {
this.text = text;
@@ -372,4 +477,79 @@ public class JavaTextContainer extends TextContainer {
this.priority = priority;
}
}
// Compute indent guide columns per line based on brace matching.
private void computeIndentGuides(List<Mark> marks) {
// Clear existing guides
for (LineData ld : lines) {
ld.indentCols.clear();
}
// Build ignored ranges from STRING and COMMENT tokens
List<int[]> ignored = new ArrayList<>();
for (Mark m : marks) {
if (m.type == TokenType.STRING || m.type == TokenType.COMMENT) {
ignored.add(new int[]{m.start, m.end});
}
}
java.util.function.Predicate<Integer> isIgnored = (pos) -> {
for (int[] r : ignored) {
if (pos >= r[0] && pos < r[1])
return true;
}
return false;
};
class OpenBrace {
int line, col;
OpenBrace(int l, int c) {
line = l;
col = c;
}
}
java.util.Deque<OpenBrace> stack = new java.util.ArrayDeque<>();
final int tabSize = 4;
for (int li = 0; li < lines.size(); li++) {
LineData ld = lines.get(li);
String s = ld.text;
for (int i = 0; i < s.length(); i++) {
int absPos = ld.start + i;
if (isIgnored.test(absPos))
continue;
char c = s.charAt(i);
if (c == '{') {
// compute leading indentation (expanded) up to this char
int leading = 0;
for (int k = 0; k < i; k++) {
char ch = s.charAt(k);
if (ch == '\t')
leading += tabSize;
else
leading += 1;
}
stack.push(new OpenBrace(li, leading));
} else if (c == '}') {
if (!stack.isEmpty()) {
OpenBrace open = stack.pop();
int startLine = open.line;
int col = open.col;
if (startLine == li)
continue; // same-line block, skip
// add guide column to lines inside the block
int from = Math.max(0, startLine + 1);
int to = Math.min(lines.size() - 1, li);
for (int l = from; l <= to; l++) {
List<Integer> list = lines.get(l).indentCols;
if (!list.contains(col))
list.add(col);
}
}
}
}
}
}
}
@@ -78,27 +78,45 @@ public class MethodBlock {
return methods;
}
// Find ranges of text to skip (strings and comments)
private static List<int[]> findExcludedRanges(String text) {
// Find ranges of text to skip (strings and comments). Returns merged, sorted ranges.
public static List<int[]> getExcludedRanges(String text) {
List<int[]> ranges = new ArrayList<>();
// Find all strings using the pattern from JavaTextContainer
// Find all strings and char literals (simple heuristic)
Pattern stringPattern = Pattern.compile("([\"'])(?:(?=(\\\\?))\\2.)*?\\1");
Matcher stringMatcher = stringPattern.matcher(text);
while (stringMatcher.find()) {
ranges.add(new int[] { stringMatcher.start(), stringMatcher.end() });
}
// Find all comments
Pattern commentPattern = Pattern.compile("/\\*[\\s\\S]*?(?:\\*/|$)|//.*|#.*");
// Find all comments (block and line comments)
Pattern commentPattern = Pattern.compile("/\\*[\\s\\S]*?(?:\\*/|$)|//.*");
Matcher commentMatcher = commentPattern.matcher(text);
while (commentMatcher.find()) {
ranges.add(new int[] { commentMatcher.start(), commentMatcher.end() });
}
if (ranges.isEmpty())
return ranges;
// Sort ranges by start position
ranges.sort((a, b) -> Integer.compare(a[0], b[0]));
return ranges;
// Merge overlapping/adjacent ranges for faster scanning
List<int[]> merged = new ArrayList<>();
int[] current = ranges.get(0);
for (int i = 1; i < ranges.size(); i++) {
int[] next = ranges.get(i);
if (next[0] <= current[1]) {
// overlap or adjacent
current[1] = Math.max(current[1], next[1]);
} else {
merged.add(current);
current = next;
}
}
merged.add(current);
return merged;
}
// Check if a position is within an excluded range
@@ -114,7 +132,7 @@ public class MethodBlock {
// Utility to find the matching closing brace, ignoring braces in strings and
// comments
public static int findMatchingBrace(String text, int openBraceIndex) {
List<int[]> excludedRanges = findExcludedRanges(text);
List<int[]> excludedRanges = getExcludedRanges(text);
int depth = 0;
for (int i = openBraceIndex; i < text.length(); i++) {