Smart auto-pair insertion/skip-over for (), [], "" and '' in GuiScriptTextArea

This commit is contained in:
bigguy345 committed 2026-02-25 13:52:47 +02:00
1 parent 2ca145b264
commit f1d902849e
1 file changed
+79 -49
@@ -1851,23 +1851,8 @@ public class GuiScriptTextArea extends GuiNpcTextField {
}
if (before.substring(0, beforeTrimEnd).endsWith("/**")) {
// Indentation is derived from the logical source line in `text` (not container lines)
// to avoid soft-wrap artifacts.
String indent = "";
if (text != null) {
int basePos = Math.max(0, Math.min(before.length(), text.length()));
int lineStart = text.lastIndexOf('\n', Math.max(0, basePos - 1)) + 1;
int p = lineStart;
while (p < text.length()) {
char ch = text.charAt(p);
if (ch == ' ' || ch == '\t') {
p++;
} else {
break;
}
}
indent = text.substring(lineStart, p);
}
// Derive indentation from the logical source line (not soft-wrapped LineData).
String indent = getLogicalLineIndentAt(Math.max(0, Math.min(cursorPos, text != null ? text.length() : 0)));
// Look ahead for a function declaration to generate smart JSDoc
String after = getSelectionAfterText();
@@ -1964,15 +1949,7 @@ public class GuiScriptTextArea extends GuiNpcTextField {
}
if (prevNonWs >= 0 && cursorPos <= (text != null ? text.length() : 0) && text.charAt(prevNonWs) == '{') {
String indent = "";
for (LineData ld : this.container.lines) {
if (prevNonWs >= ld.start && prevNonWs < ld.end) {
indent = ld.text.substring(0, IndentHelper.getLineIndent(ld.text));
break;
}
}
if (indent == null)
indent = "";
String indent = getLogicalLineIndentAt(prevNonWs);
String childIndent = indent + " ";
String after = getSelectionAfterText();
@@ -2483,15 +2460,43 @@ public class GuiScriptTextArea extends GuiNpcTextField {
String after = getSelectionAfterText();
int cursorPos = selection.getCursorPosition();
// If the user types a closing character and that same closer is
// already immediately after the caret, move caret past it instead
// of inserting another closer. This prevents duplicate closers
// when the editor auto-inserts pairs.
if ((c == ')' || c == ']' || c == '"' || c == '\'' ) && after.length() > 0 && after.charAt(0) == c) {
// Move caret forward by one (skip over existing closer)
if ((c == ')' || c == ']' || c == '}') && after.length() > 0 && after.charAt(0) == c) {
selection.reset(before.length() + 1);
scrollToCursor();
// Notify autocomplete of the character
autocompleteManager.onCharTyped(c, text, cursorPos);
return true;
}
if (c == '"' || c == '\'') {
if (after.length() > 0 && after.charAt(0) == c) {
// Move caret forward by one (skip over existing closer)
selection.reset(before.length() + 1);
scrollToCursor();
// Notify autocomplete of the character
autocompleteManager.onCharTyped(c, text, cursorPos);
return true;
}
// Auto-pair only when it looks like we're starting a new string. If the current
// logical line already has an unclosed quote of this type, treat the typed quote
// as a closer to avoid producing triple quotes.
boolean allowAutoPair = !container.getDocument().isExcludedInclusive(cursorPos)
&& !isLikelyClosingQuote(before, c);
if (allowAutoPair) {
if (c == '"') {
setText(before + "\"\"" + after, true);
} else {
setText(before + "''" + after, true);
}
selection.reset(before.length() + 1);
scrollToCursor();
autocompleteManager.onCharTyped(c, text, cursorPos);
return true;
}
addText(Character.toString(c));
scrollToCursor();
autocompleteManager.onCharTyped(c, text, cursorPos);
return true;
}
@@ -2500,22 +2505,6 @@ public class GuiScriptTextArea extends GuiNpcTextField {
// insert a matching closer and place caret between the pair.
// But only if the current position is not excluded (e.g., inside a comment or string)
if (!container.getDocument().isExcludedInclusive(cursorPos)) {
if (c == '"') {
setText(before + "\"\"" + after, true);
selection.reset(before.length() + 1);
scrollToCursor();
// Notify autocomplete of the character
autocompleteManager.onCharTyped(c, text, cursorPos);
return true;
}
if (c == '\'') {
setText(before + "''" + after, true);
selection.reset(before.length() + 1);
scrollToCursor();
// Notify autocomplete of the character
autocompleteManager.onCharTyped(c, text, cursorPos);
return true;
}
if (c == '[') {
if (hasUnmatchedCloserAhead(after, '[', ']')) {
setText(before + c + after, true);
@@ -2566,6 +2555,29 @@ public class GuiScriptTextArea extends GuiNpcTextField {
return false;
}
private boolean isLikelyClosingQuote(String before, char quote) {
if (before == null || before.isEmpty()) {
return false;
}
int lineStart = before.lastIndexOf('\n');
int start = lineStart == -1 ? 0 : lineStart + 1;
int count = 0;
for (int i = start; i < before.length(); i++) {
if (before.charAt(i) == quote && !isEscaped(before, i)) {
count++;
}
}
return (count % 2) == 1;
}
private boolean isEscaped(String s, int index) {
int backslashes = 0;
for (int i = index - 1; i >= 0 && s.charAt(i) == '\\'; i--) {
backslashes++;
}
return (backslashes % 2) == 1;
}
private boolean hasUnmatchedCloserAhead(String after, char opener, char closer) {
int balance = 0;
int len = after.length();
@@ -2588,6 +2600,24 @@ public class GuiScriptTextArea extends GuiNpcTextField {
return false;
}
private String getLogicalLineIndentAt(int pos) {
if (text == null || text.isEmpty()) {
return "";
}
int safePos = Math.max(0, Math.min(pos, text.length()));
int lineStart = text.lastIndexOf('\n', Math.max(0, safePos - 1)) + 1;
int p = lineStart;
while (p < text.length()) {
char ch = text.charAt(p);
if (ch == ' ' || ch == '\t') {
p++;
} else {
break;
}
}
return text.substring(lineStart, p);
}
private boolean isShiftKeyDown() {
return Keyboard.isKeyDown(42) || Keyboard.isKeyDown(54);
}