2023-07-30 01:26:36 -05:00
|
|
|
package noppes.npcs.client.gui.util;
|
|
|
|
|
|
|
|
|
|
import net.minecraft.client.Minecraft;
|
|
|
|
|
import net.minecraft.client.gui.GuiScreen;
|
|
|
|
|
import net.minecraft.util.ChatAllowedCharacters;
|
|
|
|
|
import noppes.npcs.NoppesStringUtils;
|
|
|
|
|
import noppes.npcs.client.ClientProxy;
|
2025-12-16 18:33:55 +02:00
|
|
|
|
2025-12-17 09:53:32 +02:00
|
|
|
import noppes.npcs.client.gui.util.script.JavaTextContainer;
|
2023-07-30 01:26:36 -05:00
|
|
|
import org.lwjgl.input.Keyboard;
|
|
|
|
|
import org.lwjgl.input.Mouse;
|
|
|
|
|
|
|
|
|
|
import java.util.ArrayList;
|
|
|
|
|
import java.util.Iterator;
|
|
|
|
|
import java.util.List;
|
|
|
|
|
import java.util.regex.Matcher;
|
2025-12-16 18:33:55 +02:00
|
|
|
|
2025-12-17 09:53:32 +02:00
|
|
|
import noppes.npcs.client.gui.util.script.JavaTextContainer.LineData;
|
2023-07-30 01:26:36 -05:00
|
|
|
|
|
|
|
|
import static net.minecraft.client.gui.GuiScreen.isCtrlKeyDown;
|
|
|
|
|
|
|
|
|
|
public class GuiScriptTextArea extends GuiNpcTextField {
|
|
|
|
|
public int id;
|
|
|
|
|
public int x;
|
|
|
|
|
public int y;
|
|
|
|
|
public int width;
|
|
|
|
|
public int height;
|
|
|
|
|
private int cursorCounter;
|
|
|
|
|
private ITextChangeListener listener;
|
|
|
|
|
public String text = null;
|
2025-12-16 18:33:55 +02:00
|
|
|
private JavaTextContainer container = null;
|
2023-07-30 01:26:36 -05:00
|
|
|
public boolean active = false;
|
|
|
|
|
public boolean enabled = true;
|
|
|
|
|
public boolean visible = true;
|
|
|
|
|
public boolean clicked = false;
|
|
|
|
|
public boolean doubleClicked = false;
|
|
|
|
|
public boolean clickScrolling = false;
|
|
|
|
|
private int startSelection;
|
|
|
|
|
private int endSelection;
|
|
|
|
|
private int cursorPosition;
|
|
|
|
|
private int scrolledLine = 0;
|
|
|
|
|
private boolean enableCodeHighlighting = false;
|
|
|
|
|
public List<GuiScriptTextArea.UndoData> undoList = new ArrayList();
|
|
|
|
|
public List<GuiScriptTextArea.UndoData> redoList = new ArrayList();
|
|
|
|
|
public boolean undoing = false;
|
|
|
|
|
private long lastClicked = 0L;
|
2023-07-30 15:47:03 -04:00
|
|
|
// private static TrueTypeFont font = new TrueTypeFont(new Font("Arial Unicode MS", Font.PLAIN, ConfigClient.FontSize), 1);
|
2023-07-30 01:26:36 -05:00
|
|
|
|
|
|
|
|
public GuiScriptTextArea(GuiScreen guiScreen, int id, int x, int y, int width, int height, String text) {
|
|
|
|
|
super(id, guiScreen, x, y, width, height, null);
|
|
|
|
|
this.id = id;
|
|
|
|
|
this.x = x;
|
|
|
|
|
this.y = y;
|
|
|
|
|
this.width = width;
|
|
|
|
|
this.height = height;
|
|
|
|
|
this.undoing = true;
|
|
|
|
|
this.setText(text);
|
|
|
|
|
this.undoing = false;
|
|
|
|
|
}
|
|
|
|
|
|
2025-03-13 17:14:45 -04:00
|
|
|
public void drawTextBox(int xMouse, int yMouse) {
|
|
|
|
|
if (!visible)
|
2023-07-30 01:26:36 -05:00
|
|
|
return;
|
2025-10-15 06:28:43 -04:00
|
|
|
clampSelectionBounds();
|
2023-07-30 01:26:36 -05:00
|
|
|
drawRect(x - 1, y - 1, x + width + 1, y + height + 1, 0xffa0a0a0);
|
|
|
|
|
drawRect(x, y, x + width, y + height, 0xff000000);
|
|
|
|
|
|
|
|
|
|
container.visibleLines = height / container.lineHeight;
|
|
|
|
|
|
2025-03-13 17:14:45 -04:00
|
|
|
if (listener instanceof GuiNPCInterface) {
|
2024-05-26 18:10:37 -04:00
|
|
|
int k2 = ((GuiNPCInterface) listener).mouseScroll = Mouse.getDWheel();
|
2025-03-13 17:14:45 -04:00
|
|
|
if (k2 != 0) {
|
|
|
|
|
this.scrolledLine -= k2 / 40;
|
2024-05-22 19:05:41 -04:00
|
|
|
this.scrolledLine = Math.max(Math.min(this.scrolledLine, this.container.linesCount - this.height / this.container.lineHeight), 0);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-03-13 17:14:45 -04:00
|
|
|
if (clicked) {
|
2023-07-30 01:26:36 -05:00
|
|
|
clicked = Mouse.isButtonDown(0);
|
|
|
|
|
int i = getSelectionPos(xMouse, yMouse);
|
2025-03-13 17:14:45 -04:00
|
|
|
if (i != cursorPosition) {
|
|
|
|
|
if (doubleClicked) {
|
2023-07-30 01:26:36 -05:00
|
|
|
startSelection = endSelection = cursorPosition;
|
|
|
|
|
doubleClicked = false;
|
|
|
|
|
}
|
|
|
|
|
setCursor(i, true);
|
|
|
|
|
}
|
2025-03-13 17:14:45 -04:00
|
|
|
} else if (doubleClicked) {
|
2023-07-30 01:26:36 -05:00
|
|
|
doubleClicked = false;
|
|
|
|
|
}
|
|
|
|
|
|
2025-03-13 17:14:45 -04:00
|
|
|
if (clickScrolling) {
|
2023-07-30 01:26:36 -05:00
|
|
|
clickScrolling = Mouse.isButtonDown(0);
|
|
|
|
|
int diff = container.linesCount - container.visibleLines;
|
2025-03-13 17:14:45 -04:00
|
|
|
scrolledLine = Math.min(Math.max((int) (1f * diff * (yMouse - y) / height), 0), diff);
|
2023-07-30 01:26:36 -05:00
|
|
|
}
|
|
|
|
|
int startBracket = 0, endBracket = 0;
|
2025-07-23 13:52:48 -04:00
|
|
|
if (startSelection >= 0 && startSelection < text.length() &&
|
2025-07-23 14:22:52 -04:00
|
|
|
(endSelection - startSelection == 1 || startSelection == endSelection)) {
|
2023-07-30 01:26:36 -05:00
|
|
|
char c = text.charAt(startSelection);
|
|
|
|
|
int found = 0;
|
2025-03-13 17:14:45 -04:00
|
|
|
if (c == '{') {
|
2023-07-30 01:26:36 -05:00
|
|
|
found = findClosingBracket(text.substring(startSelection), '{', '}');
|
2025-03-13 17:14:45 -04:00
|
|
|
} else if (c == '[') {
|
2023-07-30 01:26:36 -05:00
|
|
|
found = findClosingBracket(text.substring(startSelection), '[', ']');
|
2025-03-13 17:14:45 -04:00
|
|
|
} else if (c == '(') {
|
2023-07-30 01:26:36 -05:00
|
|
|
found = findClosingBracket(text.substring(startSelection), '(', ')');
|
2025-03-13 17:14:45 -04:00
|
|
|
} else if (c == '}') {
|
2023-07-30 01:26:36 -05:00
|
|
|
found = findOpeningBracket(text.substring(0, startSelection + 1), '{', '}');
|
2025-03-13 17:14:45 -04:00
|
|
|
} else if (c == ']') {
|
2023-07-30 01:26:36 -05:00
|
|
|
found = findOpeningBracket(text.substring(0, startSelection + 1), '[', ']');
|
2025-03-13 17:14:45 -04:00
|
|
|
} else if (c == ')') {
|
2023-07-30 01:26:36 -05:00
|
|
|
found = findOpeningBracket(text.substring(0, startSelection + 1), '(', ')');
|
|
|
|
|
}
|
2025-03-13 17:14:45 -04:00
|
|
|
if (found != 0) {
|
2023-07-30 01:26:36 -05:00
|
|
|
startBracket = startSelection;
|
|
|
|
|
endBracket = startSelection + found;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-16 18:33:55 +02:00
|
|
|
List<JavaTextContainer.LineData> list = new ArrayList<>(container.lines);
|
2023-07-30 01:26:36 -05:00
|
|
|
|
2025-12-17 18:13:29 +02:00
|
|
|
// Build brace spans: depth, open line, close line (inclusive)
|
|
|
|
|
List<int[]> braceSpans = computeBraceSpans(text, list);
|
|
|
|
|
|
2023-07-30 01:26:36 -05:00
|
|
|
String wordHightLight = null;
|
2025-03-13 17:14:45 -04:00
|
|
|
if (startSelection != endSelection) {
|
2023-07-30 01:26:36 -05:00
|
|
|
Matcher m = container.regexWord.matcher(text);
|
2025-03-13 17:14:45 -04:00
|
|
|
while (m.find()) {
|
|
|
|
|
if (m.start() == startSelection && m.end() == endSelection) {
|
2023-07-30 01:26:36 -05:00
|
|
|
wordHightLight = text.substring(startSelection, endSelection);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-03-13 17:14:45 -04:00
|
|
|
for (int i = 0; i < list.size(); i++) {
|
2023-07-30 01:26:36 -05:00
|
|
|
LineData data = list.get(i);
|
|
|
|
|
String line = data.text;
|
|
|
|
|
int w = line.length();
|
2025-03-13 17:14:45 -04:00
|
|
|
if (startBracket != endBracket) {
|
|
|
|
|
if (startBracket >= data.start && startBracket < data.end) {
|
2023-07-30 01:26:36 -05:00
|
|
|
int s = ClientProxy.Font.width(line.substring(0, startBracket - data.start));
|
|
|
|
|
int e = ClientProxy.Font.width(line.substring(0, startBracket - data.start + 1)) + 1;
|
|
|
|
|
int posY = y + 1 + (i - scrolledLine) * container.lineHeight;
|
|
|
|
|
drawRect(x + 1 + s, posY, x + 1 + e, posY + container.lineHeight + 1, 0x9900cc00);
|
|
|
|
|
}
|
2025-03-13 17:14:45 -04:00
|
|
|
if (endBracket >= data.start && endBracket < data.end) {
|
2023-07-30 01:26:36 -05:00
|
|
|
int s = ClientProxy.Font.width(line.substring(0, endBracket - data.start));
|
|
|
|
|
int e = ClientProxy.Font.width(line.substring(0, endBracket - data.start + 1)) + 1;
|
|
|
|
|
int posY = y + 1 + (i - scrolledLine) * container.lineHeight;
|
|
|
|
|
drawRect(x + 1 + s, posY, x + 1 + e, posY + container.lineHeight + 1, 0x9900cc00);
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-03-13 17:14:45 -04:00
|
|
|
if (i >= scrolledLine && i < scrolledLine + container.visibleLines) {
|
|
|
|
|
if (wordHightLight != null) {
|
2023-07-30 01:26:36 -05:00
|
|
|
Matcher m = container.regexWord.matcher(line);
|
2025-03-13 17:14:45 -04:00
|
|
|
while (m.find()) {
|
|
|
|
|
if (line.substring(m.start(), m.end()).equals(wordHightLight)) {
|
2023-07-30 01:26:36 -05:00
|
|
|
int s = ClientProxy.Font.width(line.substring(0, m.start()));
|
|
|
|
|
int e = ClientProxy.Font.width(line.substring(0, m.end())) + 1;
|
|
|
|
|
int posY = y + 1 + (i - scrolledLine) * container.lineHeight;
|
|
|
|
|
drawRect(x + 1 + s, posY, x + 1 + e, posY + container.lineHeight + 1, 0x99004c00);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-03-13 17:14:45 -04:00
|
|
|
if (startSelection != endSelection && endSelection > data.start && startSelection <= data.end) {
|
|
|
|
|
if (startSelection < data.end) {
|
2023-07-30 01:26:36 -05:00
|
|
|
int s = ClientProxy.Font.width(line.substring(0, Math.max(startSelection - data.start, 0)));
|
|
|
|
|
int e = ClientProxy.Font.width(line.substring(0, Math.min(endSelection - data.start, w))) + 1;
|
|
|
|
|
int posY = y + 1 + (i - scrolledLine) * container.lineHeight;
|
|
|
|
|
drawRect(x + 1 + s, posY, x + 1 + e, posY + container.lineHeight + 1, 0x990000ff);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
int yPos = y + (i - scrolledLine) * container.lineHeight + 1;
|
2025-12-17 18:13:29 +02:00
|
|
|
|
|
|
|
|
// Draw indent guides once per visible block based on brace spans
|
|
|
|
|
if (i == scrolledLine && !braceSpans.isEmpty()) {
|
|
|
|
|
int visStart = scrolledLine;
|
|
|
|
|
int visEnd = Math.min(list.size() - 1, scrolledLine + container.visibleLines - 1);
|
|
|
|
|
for (int[] span : braceSpans) {
|
|
|
|
|
int depth = span[0];
|
|
|
|
|
int openLine = span[1];
|
|
|
|
|
int closeLine = span[2];
|
|
|
|
|
// Skip top-level (depth 1) to avoid class/global fields guides
|
|
|
|
|
if (depth <= 1)
|
|
|
|
|
continue;
|
|
|
|
|
int startLine = openLine + 1; // start under the opening brace
|
|
|
|
|
int endLine = closeLine - 1; // stop before the closing brace
|
|
|
|
|
if (startLine > endLine)
|
|
|
|
|
continue;
|
|
|
|
|
if (endLine < visStart || startLine > visEnd)
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
int drawStart = Math.max(startLine, visStart);
|
|
|
|
|
int drawEnd = Math.min(endLine, visEnd);
|
|
|
|
|
// Compute horizontal position: 4 spaces per indent level, minus a tiny left offset
|
|
|
|
|
int spaces = (depth - 1) * 4;
|
|
|
|
|
StringBuilder sb = new StringBuilder();
|
|
|
|
|
for (int k = 0; k < spaces; k++)
|
|
|
|
|
sb.append(' ');
|
|
|
|
|
int px = ClientProxy.Font.width(sb.toString());
|
|
|
|
|
int gx = x + 4 + px - 2; // shift left ~2px for the IntelliJ feel
|
|
|
|
|
|
|
|
|
|
int topY = y + (drawStart - scrolledLine) * container.lineHeight;
|
|
|
|
|
int bottomY = y + (drawEnd - scrolledLine + 1) * container.lineHeight - 2;
|
|
|
|
|
drawRect(gx, topY, gx + 1, bottomY, 0x33FFFFFF);
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-07-30 01:26:36 -05:00
|
|
|
data.drawString(x + 1, yPos, 0xFFe0e0e0);
|
|
|
|
|
|
2025-12-17 14:33:17 +02:00
|
|
|
if (active && isEnabled() && (cursorCounter / 6) % 2 == 0 && (cursorPosition >= data.start && cursorPosition < data.end || (i == list.size() - 1 && cursorPosition == text.length()))) {
|
|
|
|
|
int posX = x + ClientProxy.Font.width(
|
|
|
|
|
line.substring(0, Math.min(cursorPosition - data.start, line.length())));
|
2023-07-30 01:26:36 -05:00
|
|
|
drawRect(posX + 1, yPos, posX + 2, yPos + 1 + container.lineHeight, -3092272);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-03-13 17:14:45 -04:00
|
|
|
if (hasVerticalScrollbar()) {
|
2023-07-30 01:26:36 -05:00
|
|
|
Minecraft.getMinecraft().renderEngine.bindTexture(GuiCustomScroll.resource);
|
|
|
|
|
int sbSize = Math.max((int) (1f * container.visibleLines / container.linesCount * height), 2);
|
|
|
|
|
|
|
|
|
|
int posX = x + width - 6;
|
2025-03-13 17:14:45 -04:00
|
|
|
int posY = (int) (y + 1f * scrolledLine / container.linesCount * (height - 4)) + 1;
|
2023-07-30 01:26:36 -05:00
|
|
|
|
|
|
|
|
drawRect(posX, posY, posX + 5, posY + sbSize, 0xFFe0e0e0);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private int findClosingBracket(String str, char s, char e) {
|
|
|
|
|
int found = 0;
|
|
|
|
|
char[] chars = str.toCharArray();
|
|
|
|
|
|
2025-03-13 17:14:45 -04:00
|
|
|
for (int i = 0; i < chars.length; ++i) {
|
2023-07-30 01:26:36 -05:00
|
|
|
char c = chars[i];
|
2025-03-13 17:14:45 -04:00
|
|
|
if (c == s) {
|
2023-07-30 01:26:36 -05:00
|
|
|
++found;
|
2025-03-13 17:14:45 -04:00
|
|
|
} else if (c == e) {
|
2023-07-30 01:26:36 -05:00
|
|
|
--found;
|
2025-03-13 17:14:45 -04:00
|
|
|
if (found == 0) {
|
2023-07-30 01:26:36 -05:00
|
|
|
return i;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private int findOpeningBracket(String str, char s, char e) {
|
|
|
|
|
int found = 0;
|
|
|
|
|
char[] chars = str.toCharArray();
|
|
|
|
|
|
2025-03-13 17:14:45 -04:00
|
|
|
for (int i = chars.length - 1; i >= 0; --i) {
|
2023-07-30 01:26:36 -05:00
|
|
|
char c = chars[i];
|
2025-03-13 17:14:45 -04:00
|
|
|
if (c == e) {
|
2023-07-30 01:26:36 -05:00
|
|
|
++found;
|
2025-03-13 17:14:45 -04:00
|
|
|
} else if (c == s) {
|
2023-07-30 01:26:36 -05:00
|
|
|
--found;
|
2025-03-13 17:14:45 -04:00
|
|
|
if (found == 0) {
|
2023-07-30 01:26:36 -05:00
|
|
|
return i - chars.length + 1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-17 18:13:29 +02:00
|
|
|
// Computes brace spans as {depth, openLineIndex, closeLineIndex}, inclusive of brace lines
|
|
|
|
|
private List<int[]> computeBraceSpans(String fullText, List<LineData> lines) {
|
|
|
|
|
List<int[]> spans = new ArrayList<>();
|
|
|
|
|
if (fullText == null || fullText.isEmpty() || lines == null || lines.isEmpty())
|
|
|
|
|
return spans;
|
|
|
|
|
|
|
|
|
|
int depth = 0;
|
|
|
|
|
int lineIdx = 0;
|
|
|
|
|
int lineEnd = lines.get(0).end;
|
|
|
|
|
java.util.ArrayList<Integer> openStack = new java.util.ArrayList<>(); // store open line indices
|
|
|
|
|
|
|
|
|
|
for (int pos = 0; pos < fullText.length(); pos++) {
|
|
|
|
|
// advance line index
|
|
|
|
|
while (lineIdx < lines.size() - 1 && pos >= lineEnd) {
|
|
|
|
|
lineIdx++;
|
|
|
|
|
lineEnd = lines.get(lineIdx).end;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
char c = fullText.charAt(pos);
|
|
|
|
|
if (c == '{') {
|
|
|
|
|
openStack.add(lineIdx);
|
|
|
|
|
depth++;
|
|
|
|
|
} else if (c == '}') {
|
|
|
|
|
if (!openStack.isEmpty()) {
|
|
|
|
|
int openLine = openStack.remove(openStack.size() - 1);
|
|
|
|
|
int spanDepth = depth; // depth before decrement
|
|
|
|
|
int closeLine = lineIdx;
|
|
|
|
|
spans.add(new int[]{spanDepth, openLine, closeLine});
|
|
|
|
|
}
|
|
|
|
|
depth = Math.max(0, depth - 1);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return spans;
|
|
|
|
|
}
|
|
|
|
|
|
2023-07-30 01:26:36 -05:00
|
|
|
private int getSelectionPos(int xMouse, int yMouse) {
|
|
|
|
|
xMouse -= this.x + 1;
|
|
|
|
|
yMouse -= this.y + 1;
|
|
|
|
|
ArrayList list = new ArrayList(this.container.lines);
|
|
|
|
|
|
2025-03-13 17:14:45 -04:00
|
|
|
for (int i = 0; i < list.size(); ++i) {
|
|
|
|
|
LineData data = (LineData) list.get(i);
|
|
|
|
|
if (i >= this.scrolledLine && i < this.scrolledLine + this.container.visibleLines) {
|
2023-07-30 01:26:36 -05:00
|
|
|
int yPos = (i - this.scrolledLine) * this.container.lineHeight;
|
2025-03-13 17:14:45 -04:00
|
|
|
if (yMouse >= yPos && yMouse < yPos + this.container.lineHeight) {
|
2023-07-30 01:26:36 -05:00
|
|
|
int lineWidth = 0;
|
|
|
|
|
char[] chars = data.text.toCharArray();
|
|
|
|
|
|
2025-03-13 17:14:45 -04:00
|
|
|
for (int j = 1; j <= chars.length; ++j) {
|
2023-07-30 01:26:36 -05:00
|
|
|
int w = ClientProxy.Font.width(data.text.substring(0, j));
|
2025-03-13 17:14:45 -04:00
|
|
|
if (xMouse < lineWidth + (w - lineWidth) / 2) {
|
2023-07-30 01:26:36 -05:00
|
|
|
return data.start + j - 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
lineWidth = w;
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-17 15:13:38 +02:00
|
|
|
// Place cursor after the last visible character of the line.
|
|
|
|
|
// `data.end - 1` previously pointed at the newline for non-last lines
|
|
|
|
|
// which made clicks land on the newline rather than after the text.
|
|
|
|
|
// Use data.start + chars.length to return the position directly
|
|
|
|
|
// after the line's characters, clamped to the total text length.
|
|
|
|
|
int posAfterChars = data.start + chars.length;
|
|
|
|
|
return Math.min(posAfterChars, text.length());
|
2023-07-30 01:26:36 -05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return this.container.text.length();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public boolean textboxKeyTyped(char c, int i) {
|
2025-03-13 17:14:45 -04:00
|
|
|
if (!active)
|
2023-07-30 01:26:36 -05:00
|
|
|
return false;
|
|
|
|
|
|
2025-03-13 17:14:45 -04:00
|
|
|
if (this.isKeyComboCtrlA(i)) {
|
2023-07-30 01:26:36 -05:00
|
|
|
startSelection = cursorPosition = 0;
|
|
|
|
|
endSelection = text.length();
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2025-03-13 17:14:45 -04:00
|
|
|
if (!isEnabled())
|
2023-07-30 01:26:36 -05:00
|
|
|
return false;
|
|
|
|
|
|
2025-12-17 14:33:17 +02:00
|
|
|
LineData currentLine = null;
|
|
|
|
|
for (LineData line : this.container.lines) {
|
|
|
|
|
if (startSelection >= line.start && startSelection <= line.end) {
|
|
|
|
|
currentLine = line;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-07-30 01:26:36 -05:00
|
|
|
String original = text;
|
2025-03-13 17:14:45 -04:00
|
|
|
if (i == Keyboard.KEY_LEFT) {
|
2023-07-30 01:26:36 -05:00
|
|
|
int j = 1;
|
2025-03-13 17:14:45 -04:00
|
|
|
if (isCtrlKeyDown()) {
|
2023-07-30 01:26:36 -05:00
|
|
|
Matcher m = container.regexWord.matcher(text.substring(0, cursorPosition));
|
2025-03-13 17:14:45 -04:00
|
|
|
while (m.find()) {
|
|
|
|
|
if (m.start() == m.end())
|
2023-07-30 01:26:36 -05:00
|
|
|
continue;
|
|
|
|
|
j = cursorPosition - m.start();
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-12-17 14:33:17 +02:00
|
|
|
int newPos = Math.max(cursorPosition - j, 0);
|
|
|
|
|
setCursor(newPos, GuiScreen.isShiftKeyDown());
|
2023-07-30 01:26:36 -05:00
|
|
|
return true;
|
|
|
|
|
}
|
2025-03-13 17:14:45 -04:00
|
|
|
if (i == Keyboard.KEY_RIGHT) {
|
2023-07-30 01:26:36 -05:00
|
|
|
int j = 1;
|
2025-03-13 17:14:45 -04:00
|
|
|
if (isCtrlKeyDown()) {
|
2023-07-30 01:26:36 -05:00
|
|
|
Matcher m = container.regexWord.matcher(text.substring(cursorPosition));
|
2025-03-13 17:14:45 -04:00
|
|
|
if (m.find() && m.start() > 0 || m.find()) {
|
2023-07-30 01:26:36 -05:00
|
|
|
j = m.start();
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-12-17 14:33:17 +02:00
|
|
|
int newPos = Math.min(cursorPosition + j, text.length()); //-2 for the \n
|
|
|
|
|
setCursor(newPos, GuiScreen.isShiftKeyDown());
|
2023-07-30 01:26:36 -05:00
|
|
|
return true;
|
|
|
|
|
}
|
2025-03-13 17:14:45 -04:00
|
|
|
if (i == Keyboard.KEY_UP) {
|
2023-07-30 01:26:36 -05:00
|
|
|
setCursor(cursorUp(), GuiScreen.isShiftKeyDown());
|
|
|
|
|
return true;
|
|
|
|
|
}
|
2025-03-13 17:14:45 -04:00
|
|
|
if (i == Keyboard.KEY_DOWN) {
|
2023-07-30 01:26:36 -05:00
|
|
|
setCursor(cursorDown(), GuiScreen.isShiftKeyDown());
|
|
|
|
|
return true;
|
|
|
|
|
}
|
2025-03-13 17:14:45 -04:00
|
|
|
if (i == Keyboard.KEY_DELETE) {
|
2023-07-30 01:26:36 -05:00
|
|
|
String s = getSelectionAfterText();
|
2025-03-13 17:14:45 -04:00
|
|
|
if (!s.isEmpty() && startSelection == endSelection)
|
2023-07-30 01:26:36 -05:00
|
|
|
s = s.substring(1);
|
|
|
|
|
setText(getSelectionBeforeText() + s);
|
|
|
|
|
endSelection = cursorPosition = startSelection;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
2025-03-13 17:14:45 -04:00
|
|
|
if (isKeyComboCtrlBackspace(i)) {
|
2023-07-30 15:47:03 -04:00
|
|
|
String s = getSelectionBeforeText();
|
2025-03-13 17:14:45 -04:00
|
|
|
if (startSelection > 0 && startSelection == endSelection) {
|
2023-07-30 15:47:03 -04:00
|
|
|
int nearestCondition = cursorPosition;
|
|
|
|
|
int g;
|
|
|
|
|
boolean cursorInWhitespace = Character.isWhitespace(s.charAt(cursorPosition - 1));
|
2025-03-13 17:14:45 -04:00
|
|
|
if (cursorInWhitespace) {
|
2023-07-30 15:47:03 -04:00
|
|
|
// Find the nearest word if we are starting in whitespace
|
|
|
|
|
for (g = cursorPosition - 1; g >= 0; g--) {
|
|
|
|
|
char currentChar = s.charAt(g);
|
|
|
|
|
if (!Character.isWhitespace(currentChar)) {
|
|
|
|
|
nearestCondition = g;
|
|
|
|
|
break;
|
|
|
|
|
}
|
2025-03-13 17:14:45 -04:00
|
|
|
if (g == 0) {
|
2023-07-30 15:47:03 -04:00
|
|
|
nearestCondition = 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-03-13 17:14:45 -04:00
|
|
|
} else {
|
2023-07-30 15:47:03 -04:00
|
|
|
// Find the nearest blank space or new line
|
|
|
|
|
for (g = cursorPosition - 1; g >= 0; g--) {
|
|
|
|
|
char currentChar = s.charAt(g);
|
2025-03-13 17:14:45 -04:00
|
|
|
if (Character.isWhitespace(currentChar) || currentChar == '\n') {
|
2023-07-30 15:47:03 -04:00
|
|
|
nearestCondition = g;
|
|
|
|
|
break;
|
|
|
|
|
}
|
2025-03-13 17:14:45 -04:00
|
|
|
if (g == 0) {
|
2023-07-30 15:47:03 -04:00
|
|
|
nearestCondition = 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Remove all text to the left up to the nearest boundary
|
|
|
|
|
s = s.substring(0, nearestCondition);
|
|
|
|
|
startSelection -= (cursorPosition - nearestCondition);
|
|
|
|
|
}
|
|
|
|
|
setText(s + getSelectionAfterText());
|
|
|
|
|
endSelection = cursorPosition = startSelection;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
2025-03-13 17:14:45 -04:00
|
|
|
if (i == Keyboard.KEY_BACK) {
|
2025-12-17 10:46:08 +02:00
|
|
|
if (startSelection == endSelection && startSelection > 0) {
|
2025-12-17 15:38:19 +02:00
|
|
|
// If the character before the cursor is a newline, remove that single newline.
|
|
|
|
|
// This handles backspacing into empty lines (joining the previous and current lines)
|
|
|
|
|
// without relying on LineData start/end calculations.
|
|
|
|
|
if (startSelection - 1 >= 0 && startSelection - 1 < text.length() && text.charAt(startSelection - 1) == '\n') {
|
|
|
|
|
String before = text.substring(0, startSelection - 1);
|
|
|
|
|
String after = startSelection < text.length() ? text.substring(startSelection) : "";
|
|
|
|
|
setText(before + after);
|
|
|
|
|
endSelection = cursorPosition = startSelection = startSelection - 1;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
2025-12-17 10:46:08 +02:00
|
|
|
// Check if we should remove an empty/whitespace-only line
|
|
|
|
|
if (currentLine != null) {
|
|
|
|
|
// Check if cursor is at line start or after only whitespace
|
|
|
|
|
String beforeCursor = text.substring(currentLine.start, startSelection);
|
|
|
|
|
boolean atLineStartOrWhitespace = beforeCursor.trim().isEmpty();
|
|
|
|
|
|
|
|
|
|
// Check if line contains only whitespace
|
|
|
|
|
boolean lineOnlyWhitespace = currentLine.text.trim().isEmpty();
|
|
|
|
|
|
|
|
|
|
if (atLineStartOrWhitespace && lineOnlyWhitespace && currentLine.start > 0) {
|
2025-12-17 15:38:19 +02:00
|
|
|
// Remove the empty/whitespace-only line by deleting the substring
|
|
|
|
|
// from the start of this line up to and including the next newline (if any).
|
|
|
|
|
int removeStart = currentLine.start;
|
|
|
|
|
int removeEnd = text.indexOf('\n', removeStart);
|
|
|
|
|
if (removeEnd == -1) {
|
|
|
|
|
removeEnd = text.length();
|
|
|
|
|
} else {
|
|
|
|
|
removeEnd = removeEnd + 1; // include the newline
|
|
|
|
|
}
|
|
|
|
|
String before = text.substring(0, removeStart);
|
|
|
|
|
String after = removeEnd > text.length() ? "" : text.substring(removeEnd);
|
2025-12-17 10:46:08 +02:00
|
|
|
setText(before + after);
|
2025-12-17 15:38:19 +02:00
|
|
|
// Place cursor at end of previous line (just before where the removed newline was)
|
|
|
|
|
int newCursor = Math.max(0, removeStart - 1);
|
|
|
|
|
endSelection = cursorPosition = startSelection = newCursor;
|
2025-12-17 10:46:08 +02:00
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-07-30 01:26:36 -05:00
|
|
|
String s = getSelectionBeforeText();
|
2025-03-13 17:14:45 -04:00
|
|
|
if (startSelection > 0 && startSelection == endSelection) {
|
2023-07-30 01:26:36 -05:00
|
|
|
s = s.substring(0, s.length() - 1);
|
|
|
|
|
startSelection--;
|
|
|
|
|
}
|
|
|
|
|
setText(s + getSelectionAfterText());
|
|
|
|
|
endSelection = cursorPosition = startSelection;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
2025-03-13 17:14:45 -04:00
|
|
|
if (this.isKeyComboCtrlX(i)) {
|
|
|
|
|
if (startSelection != endSelection) {
|
2023-07-30 01:26:36 -05:00
|
|
|
NoppesStringUtils.setClipboardContents(text.substring(startSelection, endSelection));
|
|
|
|
|
String s = getSelectionBeforeText();
|
|
|
|
|
setText(s + getSelectionAfterText());
|
|
|
|
|
endSelection = startSelection = cursorPosition = s.length();
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
2025-03-13 17:14:45 -04:00
|
|
|
if (this.isKeyComboCtrlC(i)) {
|
|
|
|
|
if (startSelection != endSelection) {
|
2023-07-30 01:26:36 -05:00
|
|
|
NoppesStringUtils.setClipboardContents(text.substring(startSelection, endSelection));
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
2025-03-13 17:14:45 -04:00
|
|
|
if (this.isKeyComboCtrlV(i)) {
|
2023-07-30 01:26:36 -05:00
|
|
|
addText(NoppesStringUtils.getClipboardContents());
|
|
|
|
|
return true;
|
|
|
|
|
}
|
2025-03-13 17:14:45 -04:00
|
|
|
if (i == Keyboard.KEY_Z && isCtrlKeyDown()) {
|
|
|
|
|
if (undoList.isEmpty())
|
2023-07-30 01:26:36 -05:00
|
|
|
return false;
|
|
|
|
|
undoing = true;
|
|
|
|
|
redoList.add(new UndoData(this.text, this.cursorPosition));
|
|
|
|
|
UndoData data = undoList.remove(undoList.size() - 1);
|
|
|
|
|
setText(data.text);
|
|
|
|
|
endSelection = startSelection = cursorPosition = data.cursorPosition;
|
|
|
|
|
undoing = false;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
2025-03-13 17:14:45 -04:00
|
|
|
if (i == Keyboard.KEY_Y && isCtrlKeyDown()) {
|
|
|
|
|
if (redoList.isEmpty())
|
2023-07-30 01:26:36 -05:00
|
|
|
return false;
|
|
|
|
|
undoing = true;
|
|
|
|
|
undoList.add(new UndoData(this.text, this.cursorPosition));
|
|
|
|
|
UndoData data = redoList.remove(redoList.size() - 1);
|
|
|
|
|
setText(data.text);
|
|
|
|
|
endSelection = startSelection = cursorPosition = data.cursorPosition;
|
|
|
|
|
undoing = false;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
2025-03-13 17:14:45 -04:00
|
|
|
if (i == Keyboard.KEY_TAB) {
|
2023-07-30 01:26:36 -05:00
|
|
|
addText(" ");
|
|
|
|
|
}
|
2025-03-13 17:14:45 -04:00
|
|
|
if (i == Keyboard.KEY_RETURN) {
|
2023-07-30 01:26:36 -05:00
|
|
|
addText(Character.toString('\n') + getIndentCurrentLine());
|
|
|
|
|
}
|
2025-03-13 17:14:45 -04:00
|
|
|
if (ChatAllowedCharacters.isAllowedCharacter(c)) {
|
2023-07-30 01:26:36 -05:00
|
|
|
addText(Character.toString(c));
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-17 09:53:32 +02:00
|
|
|
if (i == Keyboard.KEY_SLASH && isCtrlKeyDown()) {
|
2025-12-17 10:17:15 +02:00
|
|
|
if (startSelection != endSelection) {
|
2025-12-17 15:18:33 +02:00
|
|
|
toggleCommentSelection();
|
2025-12-17 10:17:15 +02:00
|
|
|
} else {
|
2025-12-17 15:18:33 +02:00
|
|
|
toggleCommentLineAtCursor();
|
2025-12-17 09:53:32 +02:00
|
|
|
}
|
2025-12-17 15:18:33 +02:00
|
|
|
return true;
|
2025-12-17 09:53:32 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (i == Keyboard.KEY_D && isCtrlKeyDown()) {
|
2025-12-17 10:17:15 +02:00
|
|
|
if (startSelection != endSelection) {
|
|
|
|
|
// Handle multi-line selection duplication
|
|
|
|
|
LineData firstLine = null, lastLine = null;
|
|
|
|
|
for (LineData line : container.lines) {
|
|
|
|
|
if (line.end > startSelection && line.start < endSelection) {
|
|
|
|
|
if (firstLine == null) firstLine = line;
|
|
|
|
|
lastLine = line;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (firstLine != null && lastLine != null) {
|
|
|
|
|
String selectedText = text.substring(firstLine.start, lastLine.end);
|
|
|
|
|
String insertText = "\n" + selectedText;
|
|
|
|
|
// Save selection before setText
|
|
|
|
|
int savedStart = startSelection;
|
|
|
|
|
int savedEnd = endSelection;
|
|
|
|
|
setText(text.substring(0, lastLine.end) + insertText + text.substring(lastLine.end));
|
|
|
|
|
// Move cursor to start of duplicated block
|
|
|
|
|
setCursor(lastLine.end + 1, false);
|
|
|
|
|
// Restore selection
|
|
|
|
|
startSelection = savedStart;
|
|
|
|
|
endSelection = savedEnd;
|
2025-12-17 09:53:32 +02:00
|
|
|
return true;
|
|
|
|
|
}
|
2025-12-17 10:17:15 +02:00
|
|
|
} else {
|
|
|
|
|
// Single line duplication (existing logic)
|
|
|
|
|
for (LineData line : container.lines) {
|
|
|
|
|
if (cursorPosition >= line.start && cursorPosition <= line.end) {
|
|
|
|
|
int lineStart = line.start, lineEnd = line.end;
|
|
|
|
|
String lineText = text.substring(lineStart, lineEnd);
|
|
|
|
|
String insertText = "\n" + lineText;
|
|
|
|
|
// Save selection before setText
|
|
|
|
|
int savedStart = startSelection;
|
|
|
|
|
int savedEnd = endSelection;
|
|
|
|
|
setText(text.substring(0, lineEnd) + insertText + text.substring(lineEnd));
|
|
|
|
|
// Move cursor to start of duplicated line
|
|
|
|
|
setCursor(lineEnd + 1, false);
|
|
|
|
|
// Restore selection
|
|
|
|
|
startSelection = savedStart;
|
|
|
|
|
endSelection = savedEnd;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-12-17 09:53:32 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-07-30 01:26:36 -05:00
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2025-03-13 17:14:45 -04:00
|
|
|
private boolean isShiftKeyDown() {
|
2023-07-30 01:26:36 -05:00
|
|
|
return Keyboard.isKeyDown(42) || Keyboard.isKeyDown(54);
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-17 15:18:33 +02:00
|
|
|
private void toggleCommentSelection() {
|
|
|
|
|
StringBuilder newText = new StringBuilder(text.length() + 100);
|
|
|
|
|
int prevEnd = 0;
|
|
|
|
|
|
|
|
|
|
int newStart = -1; //calculates expanded startSelection
|
|
|
|
|
int newEnd = -1; //calculates expanded endSelection
|
|
|
|
|
int newIndex = 0;
|
|
|
|
|
|
|
|
|
|
for (LineData line : container.lines) {
|
|
|
|
|
int safeLineStart = Math.max(0, Math.min(line.start, text.length()));
|
|
|
|
|
int safeLineEnd = Math.max(0, Math.min(line.end, text.length()));
|
|
|
|
|
|
|
|
|
|
if (safeLineStart > prevEnd) {
|
|
|
|
|
if (newStart == -1 && startSelection >= prevEnd && startSelection <= safeLineStart) {
|
|
|
|
|
newStart = newIndex + (startSelection - prevEnd);
|
|
|
|
|
}
|
|
|
|
|
if (newEnd == -1 && endSelection >= prevEnd && endSelection <= safeLineStart) {
|
|
|
|
|
newEnd = newIndex + (endSelection - prevEnd);
|
|
|
|
|
}
|
|
|
|
|
newText.append(text, prevEnd, safeLineStart);
|
|
|
|
|
newIndex += safeLineStart - prevEnd;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (safeLineEnd > startSelection && safeLineStart < endSelection) {
|
|
|
|
|
String lineText = text.substring(safeLineStart, safeLineEnd);
|
|
|
|
|
CommentToggleInfo ti = toggleLine(lineText);
|
|
|
|
|
String newLineText = ti.text;
|
|
|
|
|
int nonWs = ti.nonWs;
|
|
|
|
|
int delta = ti.delta;
|
|
|
|
|
|
|
|
|
|
if (newStart == -1 && startSelection >= safeLineStart && startSelection <= safeLineEnd) {
|
|
|
|
|
int offsetInOldStart = startSelection - safeLineStart;
|
|
|
|
|
int newOffsetStart = offsetInOldStart;
|
|
|
|
|
if (offsetInOldStart >= nonWs) {
|
|
|
|
|
newOffsetStart = offsetInOldStart + delta;
|
|
|
|
|
}
|
|
|
|
|
newStart = newIndex + Math.max(0, newOffsetStart);
|
|
|
|
|
}
|
|
|
|
|
if (newEnd == -1 && endSelection >= safeLineStart && endSelection <= safeLineEnd) {
|
|
|
|
|
int offsetInOld = endSelection - safeLineStart;
|
|
|
|
|
int newOffset = offsetInOld;
|
|
|
|
|
if (offsetInOld >= nonWs) {
|
|
|
|
|
newOffset = offsetInOld + delta;
|
|
|
|
|
}
|
|
|
|
|
newEnd = newIndex + Math.max(0, newOffset);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
newText.append(newLineText);
|
|
|
|
|
newIndex += newLineText.length();
|
|
|
|
|
} else {
|
|
|
|
|
if (newStart == -1 && startSelection >= safeLineStart && startSelection <= safeLineEnd) {
|
|
|
|
|
newStart = newIndex + (startSelection - safeLineStart);
|
|
|
|
|
}
|
|
|
|
|
if (newEnd == -1 && endSelection >= safeLineStart && endSelection <= safeLineEnd) {
|
|
|
|
|
newEnd = newIndex + (endSelection - safeLineStart);
|
|
|
|
|
}
|
|
|
|
|
newText.append(text, safeLineStart, safeLineEnd);
|
|
|
|
|
newIndex += safeLineEnd - safeLineStart;
|
|
|
|
|
}
|
|
|
|
|
prevEnd = safeLineEnd;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (prevEnd < text.length()) {
|
|
|
|
|
if (newStart == -1 && startSelection >= prevEnd && startSelection <= text.length()) {
|
|
|
|
|
newStart = newIndex + (startSelection - prevEnd);
|
|
|
|
|
}
|
|
|
|
|
if (newEnd == -1 && endSelection >= prevEnd && endSelection <= text.length()) {
|
|
|
|
|
newEnd = newIndex + (endSelection - prevEnd);
|
|
|
|
|
}
|
|
|
|
|
newText.append(text, prevEnd, text.length());
|
|
|
|
|
newIndex += text.length() - prevEnd;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (newStart == -1) newStart = Math.max(0, Math.min(startSelection, newText.length()));
|
|
|
|
|
if (newEnd == -1) newEnd = Math.max(0, Math.min(endSelection, newText.length()));
|
|
|
|
|
|
|
|
|
|
setText(newText.toString());
|
|
|
|
|
startSelection = newStart;
|
|
|
|
|
endSelection = newEnd;
|
|
|
|
|
cursorPosition = endSelection;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void toggleCommentLineAtCursor() {
|
|
|
|
|
for (LineData line : container.lines) {
|
|
|
|
|
int lineStart = Math.max(0, Math.min(line.start, text.length()));
|
|
|
|
|
int lineEnd = Math.max(0, Math.min(line.end, text.length()));
|
|
|
|
|
if (cursorPosition >= lineStart && cursorPosition <= lineEnd) {
|
|
|
|
|
String lineText = text.substring(lineStart, lineEnd);
|
|
|
|
|
CommentToggleInfo ti = toggleLine(lineText);
|
|
|
|
|
String newLineText = ti.text;
|
|
|
|
|
int nonWs = ti.nonWs;
|
|
|
|
|
boolean hasComment = ti.delta < 0;
|
|
|
|
|
String before = text.substring(0, lineStart);
|
|
|
|
|
String after = lineEnd <= text.length() ? text.substring(lineEnd) : "";
|
|
|
|
|
setText(before + newLineText + after);
|
|
|
|
|
int cursorDelta = hasComment ? -2 : 2;
|
|
|
|
|
int newCursor = cursorPosition + cursorDelta;
|
|
|
|
|
if (cursorPosition < lineStart + nonWs + (hasComment ? 2 : 0)) newCursor = cursorPosition;
|
|
|
|
|
setCursor(Math.max(lineStart, newCursor), false);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-03-13 17:14:45 -04:00
|
|
|
private boolean isAltKeyDown() {
|
2023-07-30 01:26:36 -05:00
|
|
|
return Keyboard.isKeyDown(56) || Keyboard.isKeyDown(184);
|
|
|
|
|
}
|
|
|
|
|
|
2025-03-13 17:14:45 -04:00
|
|
|
private boolean isKeyComboCtrlX(int keyID) {
|
2023-07-30 01:26:36 -05:00
|
|
|
return keyID == 45 && isCtrlKeyDown() && !isShiftKeyDown() && !isAltKeyDown();
|
|
|
|
|
}
|
|
|
|
|
|
2025-03-13 17:14:45 -04:00
|
|
|
private boolean isKeyComboCtrlBackspace(int keyID) {
|
2023-07-30 15:47:03 -04:00
|
|
|
return keyID == Keyboard.KEY_BACK && isCtrlKeyDown() && !isShiftKeyDown() && !isAltKeyDown();
|
|
|
|
|
}
|
|
|
|
|
|
2025-03-13 17:14:45 -04:00
|
|
|
private boolean isKeyComboCtrlV(int keyID) {
|
2023-07-30 01:26:36 -05:00
|
|
|
return keyID == 47 && isCtrlKeyDown() && !isShiftKeyDown() && !isAltKeyDown();
|
|
|
|
|
}
|
|
|
|
|
|
2025-03-13 17:14:45 -04:00
|
|
|
private boolean isKeyComboCtrlC(int keyID) {
|
2023-07-30 01:26:36 -05:00
|
|
|
return keyID == 46 && isCtrlKeyDown() && !isShiftKeyDown() && !isAltKeyDown();
|
|
|
|
|
}
|
|
|
|
|
|
2025-03-13 17:14:45 -04:00
|
|
|
private boolean isKeyComboCtrlA(int keyID) {
|
2023-07-30 01:26:36 -05:00
|
|
|
return keyID == 30 && isCtrlKeyDown() && !isShiftKeyDown() && !isAltKeyDown();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private String getIndentCurrentLine() {
|
|
|
|
|
Iterator var1 = this.container.lines.iterator();
|
|
|
|
|
|
|
|
|
|
LineData data;
|
|
|
|
|
do {
|
2025-03-13 17:14:45 -04:00
|
|
|
if (!var1.hasNext()) {
|
2023-07-30 01:26:36 -05:00
|
|
|
return "";
|
|
|
|
|
}
|
|
|
|
|
|
2025-03-13 17:14:45 -04:00
|
|
|
data = (LineData) var1.next();
|
|
|
|
|
} while (this.cursorPosition <= data.start || this.cursorPosition > data.end);
|
2023-07-30 01:26:36 -05:00
|
|
|
|
|
|
|
|
int i;
|
2025-03-13 17:14:45 -04:00
|
|
|
for (i = 0; i < data.text.length() && data.text.charAt(i) == 32; ++i) {
|
2023-07-30 01:26:36 -05:00
|
|
|
;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return data.text.substring(0, i);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void setCursor(int i, boolean select) {
|
|
|
|
|
i = Math.min(Math.max(i, 0), this.text.length());
|
2025-03-13 17:14:45 -04:00
|
|
|
if (i != this.cursorPosition) {
|
|
|
|
|
if (!select) {
|
2023-07-30 01:26:36 -05:00
|
|
|
this.endSelection = this.startSelection = this.cursorPosition = i;
|
|
|
|
|
} else {
|
|
|
|
|
int diff = this.cursorPosition - i;
|
2025-03-13 17:14:45 -04:00
|
|
|
if (this.cursorPosition == this.startSelection) {
|
2023-07-30 01:26:36 -05:00
|
|
|
this.startSelection -= diff;
|
2025-03-13 17:14:45 -04:00
|
|
|
} else if (this.cursorPosition == this.endSelection) {
|
2023-07-30 01:26:36 -05:00
|
|
|
this.endSelection -= diff;
|
|
|
|
|
}
|
|
|
|
|
|
2025-03-13 17:14:45 -04:00
|
|
|
if (this.startSelection > this.endSelection) {
|
2023-07-30 01:26:36 -05:00
|
|
|
int j = this.endSelection;
|
|
|
|
|
this.endSelection = this.startSelection;
|
|
|
|
|
this.startSelection = j;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this.cursorPosition = i;
|
|
|
|
|
}
|
2025-10-15 06:28:43 -04:00
|
|
|
|
|
|
|
|
clampSelectionBounds();
|
2023-07-30 01:26:36 -05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void addText(String s) {
|
|
|
|
|
this.setText(this.getSelectionBeforeText() + s + this.getSelectionAfterText());
|
|
|
|
|
this.endSelection = this.startSelection = this.cursorPosition = this.startSelection + s.length();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private int cursorUp() {
|
2025-03-13 17:14:45 -04:00
|
|
|
for (int i = 0; i < this.container.lines.size(); ++i) {
|
|
|
|
|
LineData data = (LineData) this.container.lines.get(i);
|
|
|
|
|
if (this.cursorPosition >= data.start && this.cursorPosition < data.end) {
|
|
|
|
|
if (i == 0) {
|
2023-07-30 01:26:36 -05:00
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int var10000 = this.cursorPosition - data.start;
|
|
|
|
|
return this.getSelectionPos(this.x + 1 + ClientProxy.Font.width(data.text.substring(0, this.cursorPosition - data.start)), this.y + 1 + (i - 1 - this.scrolledLine) * this.container.lineHeight);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private int cursorDown() {
|
2025-12-17 14:33:17 +02:00
|
|
|
if (cursorPosition == text.length())
|
|
|
|
|
return cursorPosition;
|
2025-03-13 17:14:45 -04:00
|
|
|
for (int i = 0; i < this.container.lines.size(); ++i) {
|
|
|
|
|
LineData data = (LineData) this.container.lines.get(i);
|
|
|
|
|
if (this.cursorPosition >= data.start && this.cursorPosition < data.end) {
|
2023-07-30 01:26:36 -05:00
|
|
|
int var10000 = this.cursorPosition - data.start;
|
|
|
|
|
return this.getSelectionPos(this.x + 1 + ClientProxy.Font.width(data.text.substring(0, this.cursorPosition - data.start)), this.y + 1 + (i + 1 - this.scrolledLine) * this.container.lineHeight);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return this.text.length();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public String getSelectionBeforeText() {
|
2025-03-13 17:14:45 -04:00
|
|
|
return this.startSelection == 0 ? "" : this.text.substring(0, this.startSelection);
|
2023-07-30 01:26:36 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public String getSelectionAfterText() {
|
|
|
|
|
return this.text.substring(this.endSelection);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void mouseClicked(int xMouse, int yMouse, int mouseButton) {
|
|
|
|
|
this.active = xMouse >= this.x && xMouse < this.x + this.width && yMouse >= this.y && yMouse < this.y + this.height;
|
2025-03-13 17:14:45 -04:00
|
|
|
if (this.active) {
|
2023-07-30 01:26:36 -05:00
|
|
|
this.startSelection = this.endSelection = this.cursorPosition = this.getSelectionPos(xMouse, yMouse);
|
|
|
|
|
this.clicked = mouseButton == 0;
|
|
|
|
|
this.doubleClicked = false;
|
|
|
|
|
long time = System.currentTimeMillis();
|
2025-03-13 17:14:45 -04:00
|
|
|
if (this.clicked && this.container.linesCount * this.container.lineHeight > this.height && xMouse > this.x + this.width - 8) {
|
2023-07-30 01:26:36 -05:00
|
|
|
this.clicked = false;
|
|
|
|
|
this.clickScrolling = true;
|
2025-03-13 17:14:45 -04:00
|
|
|
} else if (time - this.lastClicked < 500L) {
|
2023-07-30 01:26:36 -05:00
|
|
|
this.doubleClicked = true;
|
|
|
|
|
Matcher m = this.container.regexWord.matcher(this.text);
|
|
|
|
|
|
2025-03-13 17:14:45 -04:00
|
|
|
while (m.find()) {
|
|
|
|
|
if (this.cursorPosition > m.start() && this.cursorPosition < m.end()) {
|
2023-07-30 01:26:36 -05:00
|
|
|
this.startSelection = m.start();
|
|
|
|
|
this.endSelection = m.end();
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this.lastClicked = time;
|
|
|
|
|
activeTextfield = this;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void updateCursorCounter() {
|
|
|
|
|
++this.cursorCounter;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void setText(String text) {
|
|
|
|
|
if (text == null) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
text = text.replace("\r", "");
|
|
|
|
|
text = text.replace("\t", " ");
|
2025-03-13 17:14:45 -04:00
|
|
|
if (this.text == null || !this.text.equals(text)) {
|
|
|
|
|
if (this.listener != null) {
|
2023-07-30 01:26:36 -05:00
|
|
|
this.listener.textUpdate(text);
|
|
|
|
|
}
|
|
|
|
|
|
2025-03-13 17:14:45 -04:00
|
|
|
if (!this.undoing) {
|
2023-07-30 01:26:36 -05:00
|
|
|
this.undoList.add(new GuiScriptTextArea.UndoData(this.text, this.cursorPosition));
|
|
|
|
|
this.redoList.clear();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this.text = text;
|
2025-12-16 18:33:55 +02:00
|
|
|
//this.container = new TextContainer(text);
|
|
|
|
|
this.container = new JavaTextContainer(text);
|
2023-07-30 01:26:36 -05:00
|
|
|
this.container.init(this.width, this.height);
|
2025-03-13 17:14:45 -04:00
|
|
|
if (this.enableCodeHighlighting) {
|
2023-07-30 01:26:36 -05:00
|
|
|
this.container.formatCodeText();
|
|
|
|
|
}
|
|
|
|
|
|
2025-03-13 17:14:45 -04:00
|
|
|
if (this.scrolledLine > this.container.linesCount - this.container.visibleLines) {
|
2023-07-30 01:26:36 -05:00
|
|
|
this.scrolledLine = Math.max(0, this.container.linesCount - this.container.visibleLines);
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-15 06:28:43 -04:00
|
|
|
clampSelectionBounds();
|
|
|
|
|
|
2023-07-30 01:26:36 -05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public String getText() {
|
|
|
|
|
return this.text;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public boolean isEnabled() {
|
|
|
|
|
return this.enabled && this.visible;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public boolean hasVerticalScrollbar() {
|
|
|
|
|
return this.container.visibleLines < this.container.linesCount;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void enableCodeHighlighting() {
|
|
|
|
|
this.enableCodeHighlighting = true;
|
|
|
|
|
this.container.formatCodeText();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void setListener(ITextChangeListener listener) {
|
|
|
|
|
this.listener = listener;
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-15 06:28:43 -04:00
|
|
|
private void clampSelectionBounds() {
|
|
|
|
|
if (this.text == null || this.text.isEmpty()) {
|
|
|
|
|
this.startSelection = this.endSelection = this.cursorPosition = 0;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int length = this.text.length();
|
|
|
|
|
this.startSelection = Math.max(0, Math.min(this.startSelection, length));
|
|
|
|
|
this.endSelection = Math.max(0, Math.min(this.endSelection, length));
|
|
|
|
|
this.cursorPosition = Math.max(0, Math.min(this.cursorPosition, length));
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-17 15:18:33 +02:00
|
|
|
private static class CommentToggleInfo {
|
|
|
|
|
public final String text;
|
|
|
|
|
public final int delta; // newText.length() - oldText.length()
|
|
|
|
|
public final int nonWs;
|
|
|
|
|
|
|
|
|
|
public CommentToggleInfo(String text, int delta, int nonWs) {
|
|
|
|
|
this.text = text;
|
|
|
|
|
this.delta = delta;
|
|
|
|
|
this.nonWs = nonWs;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private CommentToggleInfo toggleLine(String lineText) {
|
|
|
|
|
int nonWs = 0;
|
|
|
|
|
while (nonWs < lineText.length() && Character.isWhitespace(lineText.charAt(nonWs))) nonWs++;
|
|
|
|
|
boolean hasContent = nonWs < lineText.length();
|
|
|
|
|
boolean hasComment = hasContent && lineText.startsWith("//", nonWs);
|
|
|
|
|
String newLineText;
|
|
|
|
|
if (hasComment) {
|
|
|
|
|
int cut = Math.min(nonWs + 2, lineText.length());
|
|
|
|
|
newLineText = lineText.substring(0, nonWs) + (cut <= lineText.length() ? lineText.substring(cut) : "");
|
|
|
|
|
} else if (hasContent) {
|
|
|
|
|
newLineText = lineText.substring(0, nonWs) + "//" + lineText.substring(nonWs);
|
|
|
|
|
} else {
|
|
|
|
|
newLineText = lineText;
|
|
|
|
|
}
|
|
|
|
|
return new CommentToggleInfo(newLineText, newLineText.length() - lineText.length(), nonWs);
|
|
|
|
|
}
|
|
|
|
|
|
2023-07-30 01:26:36 -05:00
|
|
|
class UndoData {
|
|
|
|
|
public String text;
|
|
|
|
|
public int cursorPosition;
|
|
|
|
|
|
|
|
|
|
public UndoData(String text, int cursorPosition) {
|
|
|
|
|
this.text = text;
|
|
|
|
|
this.cursorPosition = cursorPosition;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|