kopia lustrzana https://github.com/maccasoft/z80-tools
1110 wiersze
38 KiB
Java
1110 wiersze
38 KiB
Java
/*
|
|
* Copyright (c) 2018 Marco Maccaferri and others.
|
|
* All rights reserved.
|
|
*
|
|
* This program and the accompanying materials are made available under
|
|
* the terms of the Eclipse Public License v1.0 which accompanies this
|
|
* distribution, and is available at
|
|
* http://www.eclipse.org/legal/epl-v10.html
|
|
*/
|
|
|
|
package com.maccasoft.tools.editor;
|
|
|
|
import java.util.ArrayList;
|
|
import java.util.HashMap;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
import java.util.Stack;
|
|
|
|
import org.eclipse.jface.dialogs.IInputValidator;
|
|
import org.eclipse.jface.dialogs.InputDialog;
|
|
import org.eclipse.jface.resource.StringConverter;
|
|
import org.eclipse.swt.SWT;
|
|
import org.eclipse.swt.custom.CTabFolder;
|
|
import org.eclipse.swt.custom.CaretEvent;
|
|
import org.eclipse.swt.custom.CaretListener;
|
|
import org.eclipse.swt.custom.LineStyleEvent;
|
|
import org.eclipse.swt.custom.LineStyleListener;
|
|
import org.eclipse.swt.custom.MovementEvent;
|
|
import org.eclipse.swt.custom.MovementListener;
|
|
import org.eclipse.swt.custom.ST;
|
|
import org.eclipse.swt.custom.StyleRange;
|
|
import org.eclipse.swt.custom.StyledText;
|
|
import org.eclipse.swt.custom.VerifyKeyListener;
|
|
import org.eclipse.swt.events.DisposeEvent;
|
|
import org.eclipse.swt.events.DisposeListener;
|
|
import org.eclipse.swt.events.KeyAdapter;
|
|
import org.eclipse.swt.events.KeyEvent;
|
|
import org.eclipse.swt.events.ModifyEvent;
|
|
import org.eclipse.swt.events.ModifyListener;
|
|
import org.eclipse.swt.events.TraverseEvent;
|
|
import org.eclipse.swt.events.TraverseListener;
|
|
import org.eclipse.swt.events.VerifyEvent;
|
|
import org.eclipse.swt.events.VerifyListener;
|
|
import org.eclipse.swt.graphics.Color;
|
|
import org.eclipse.swt.graphics.Font;
|
|
import org.eclipse.swt.graphics.FontData;
|
|
import org.eclipse.swt.graphics.GC;
|
|
import org.eclipse.swt.graphics.Point;
|
|
import org.eclipse.swt.graphics.Rectangle;
|
|
import org.eclipse.swt.graphics.TextStyle;
|
|
import org.eclipse.swt.layout.GridData;
|
|
import org.eclipse.swt.layout.GridLayout;
|
|
import org.eclipse.swt.widgets.Caret;
|
|
import org.eclipse.swt.widgets.Composite;
|
|
import org.eclipse.swt.widgets.Control;
|
|
import org.eclipse.swt.widgets.Display;
|
|
import org.eclipse.swt.widgets.Event;
|
|
|
|
public class SourceEditor {
|
|
|
|
private static final int UNDO_LIMIT = 500;
|
|
private static final int CURRENT_CHANGE_TIMER_EXPIRE = 500;
|
|
|
|
Composite container;
|
|
LineNumbersRuler ruler;
|
|
StyledText text;
|
|
TokenMarker tokenMarker;
|
|
|
|
private int currentLine;
|
|
private Color currentLineBackground;
|
|
|
|
private Font font;
|
|
private Font fontBold;
|
|
private Map<TokenId, TextStyle> styleMap = new HashMap<TokenId, TextStyle>();
|
|
|
|
Caret insertCaret;
|
|
Caret overwriteCaret;
|
|
SearchBox searchBox;
|
|
|
|
TextChange currentChange;
|
|
Stack<TextChange> undoStack = new Stack<TextChange>();
|
|
Stack<TextChange> redoStack = new Stack<TextChange>();
|
|
boolean ignoreUndo;
|
|
boolean ignoreRedo;
|
|
|
|
boolean showLineNumbers = true;
|
|
int[] tabStops;
|
|
boolean useTabstops;
|
|
|
|
class TextChange {
|
|
|
|
int caretOffset;
|
|
int topIndex;
|
|
int start;
|
|
int length;
|
|
String replacedText;
|
|
long timeStamp;
|
|
|
|
TextChange(int start, int length, String replacedText, int topIndex, int caretOffset) {
|
|
this.start = start;
|
|
this.length = length;
|
|
this.replacedText = replacedText;
|
|
this.timeStamp = System.currentTimeMillis();
|
|
this.topIndex = topIndex;
|
|
this.caretOffset = caretOffset;
|
|
}
|
|
|
|
void append(int length, String replacedText) {
|
|
this.length += length;
|
|
this.replacedText += replacedText;
|
|
this.timeStamp = System.currentTimeMillis();
|
|
}
|
|
|
|
boolean isExpired() {
|
|
return (System.currentTimeMillis() - timeStamp) >= CURRENT_CHANGE_TIMER_EXPIRE;
|
|
}
|
|
}
|
|
|
|
private final CaretListener caretListener = new CaretListener() {
|
|
|
|
@Override
|
|
public void caretMoved(CaretEvent event) {
|
|
int offset = text.getCaretOffset();
|
|
int line = text.getLineAtOffset(offset);
|
|
if (line != currentLine) {
|
|
if (currentLine >= 0 && currentLine < text.getLineCount()) {
|
|
text.setLineBackground(currentLine, 1, null);
|
|
}
|
|
text.setLineBackground(line, 1, currentLineBackground);
|
|
currentLine = line;
|
|
}
|
|
}
|
|
};
|
|
|
|
final Runnable refreshMarkersRunnable = new Runnable() {
|
|
|
|
@Override
|
|
public void run() {
|
|
if (text == null || text.isDisposed()) {
|
|
return;
|
|
}
|
|
tokenMarker.refreshMultilineComments(text.getText());
|
|
text.redraw();
|
|
}
|
|
};
|
|
|
|
public SourceEditor(Composite parent, TokenMarker tokenMarker) {
|
|
this.tokenMarker = tokenMarker;
|
|
createTextEditor(parent);
|
|
}
|
|
|
|
protected void createTextEditor(Composite parent) {
|
|
container = new Composite(parent, SWT.NONE);
|
|
GridLayout containerLayout = new GridLayout(2, false);
|
|
containerLayout.horizontalSpacing = 1;
|
|
containerLayout.marginWidth = containerLayout.marginHeight = 0;
|
|
container.setLayout(containerLayout);
|
|
|
|
if ("win32".equals(SWT.getPlatform())) {
|
|
font = new Font(Display.getDefault(), "Courier New", 9, SWT.NONE);
|
|
fontBold = new Font(Display.getDefault(), "Courier New", 9, SWT.BOLD);
|
|
}
|
|
else {
|
|
font = new Font(Display.getDefault(), "mono", 9, SWT.NONE);
|
|
fontBold = new Font(Display.getDefault(), "mono", 9, SWT.BOLD);
|
|
}
|
|
|
|
ruler = new LineNumbersRuler(container);
|
|
|
|
text = new StyledText(container, SWT.FULL_SELECTION | SWT.H_SCROLL | SWT.V_SCROLL) {
|
|
|
|
@Override
|
|
public void invokeAction(int action) {
|
|
switch (action) {
|
|
case ST.LINE_START:
|
|
SourceEditor.this.doLineStart(false);
|
|
break;
|
|
case ST.LINE_END:
|
|
SourceEditor.this.doLineEnd(false);
|
|
break;
|
|
case ST.SELECT_LINE_START:
|
|
SourceEditor.this.doLineStart(true);
|
|
break;
|
|
case ST.SELECT_LINE_END:
|
|
SourceEditor.this.doLineEnd(true);
|
|
break;
|
|
case ST.PASTE:
|
|
SourceEditor.this.paste();
|
|
break;
|
|
default:
|
|
super.invokeAction(action);
|
|
}
|
|
}
|
|
};
|
|
text.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
|
|
text.setMargins(5, 5, 5, 5);
|
|
text.setTabs(4);
|
|
text.setFont(font);
|
|
|
|
ruler.setFont(font);
|
|
ruler.setText(text);
|
|
|
|
insertCaret = createInsertCaret(text);
|
|
overwriteCaret = createOverwriteCaret(text);
|
|
text.setCaret(insertCaret);
|
|
|
|
currentLine = 0;
|
|
currentLineBackground = new Color(Display.getDefault(), 232, 242, 254);
|
|
text.setLineBackground(currentLine, 1, currentLineBackground);
|
|
|
|
updateTokenStyles();
|
|
|
|
text.addCaretListener(caretListener);
|
|
text.addTraverseListener(new TraverseListener() {
|
|
|
|
@Override
|
|
public void keyTraversed(TraverseEvent e) {
|
|
if (e.character != SWT.TAB || (e.stateMask & SWT.MODIFIER_MASK) == 0) {
|
|
return;
|
|
}
|
|
final Event e1 = new Event();
|
|
e1.character = e.character;
|
|
e1.stateMask = e.stateMask;
|
|
e1.detail = e.detail;
|
|
e.display.asyncExec(new Runnable() {
|
|
|
|
@Override
|
|
public void run() {
|
|
if (!text.isDisposed()) {
|
|
Control control = text.getParent();
|
|
while (!(control instanceof CTabFolder) && control.getParent() != null) {
|
|
control = control.getParent();
|
|
}
|
|
control.notifyListeners(SWT.Traverse, e1);
|
|
}
|
|
}
|
|
});
|
|
e.doit = false;
|
|
}
|
|
});
|
|
text.addKeyListener(new KeyAdapter() {
|
|
|
|
@Override
|
|
public void keyPressed(KeyEvent e) {
|
|
if (e.keyCode == 'f') {
|
|
if ((e.stateMask & SWT.MODIFIER_MASK) == SWT.MOD1) {
|
|
if (searchBox == null) {
|
|
searchBox = new SearchBox(text);
|
|
}
|
|
String selection = text.getSelectionText();
|
|
if (!selection.isEmpty()) {
|
|
searchBox.setLastSearch(selection);
|
|
}
|
|
searchBox.open();
|
|
}
|
|
}
|
|
else if (e.keyCode == 'k') {
|
|
if ((e.stateMask & SWT.MODIFIER_MASK) == SWT.MOD1) {
|
|
if (searchBox == null) {
|
|
searchBox = new SearchBox(text);
|
|
}
|
|
searchBox.searchNext();
|
|
}
|
|
}
|
|
else if (e.keyCode == 'l') {
|
|
if ((e.stateMask & SWT.MODIFIER_MASK) == SWT.MOD1) {
|
|
goToLine();
|
|
}
|
|
}
|
|
else if (e.keyCode == SWT.INSERT && e.stateMask == 0) {
|
|
text.setCaret(text.getCaret() == insertCaret ? overwriteCaret : insertCaret);
|
|
}
|
|
}
|
|
});
|
|
text.addVerifyKeyListener(new VerifyKeyListener() {
|
|
|
|
@Override
|
|
public void verifyKey(VerifyEvent e) {
|
|
if (e.keyCode == SWT.CR) {
|
|
handleAutoIndent();
|
|
e.doit = false;
|
|
}
|
|
else if (e.keyCode == SWT.TAB) {
|
|
e.doit = false;
|
|
if ((e.stateMask & SWT.CTRL) != 0) {
|
|
return;
|
|
}
|
|
doTab();
|
|
}
|
|
else if (e.keyCode == SWT.BS) {
|
|
e.doit = doBackspace();
|
|
}
|
|
}
|
|
});
|
|
|
|
text.addVerifyListener(new VerifyListener() {
|
|
|
|
@Override
|
|
public void verifyText(VerifyEvent e) {
|
|
String replacedText = text.getTextRange(e.start, e.end - e.start);
|
|
if (!ignoreUndo) {
|
|
if (currentChange == null || currentChange.isExpired()) {
|
|
undoStack.push(currentChange = new TextChange(e.start, e.text.length(), replacedText, text.getTopIndex(), text.getCaretOffset()));
|
|
if (undoStack.size() > UNDO_LIMIT) {
|
|
undoStack.remove(0);
|
|
}
|
|
}
|
|
else {
|
|
if (e.start != currentChange.start + currentChange.length) {
|
|
undoStack.push(currentChange = new TextChange(e.start, e.text.length(), replacedText, text.getTopIndex(), text.getCaretOffset()));
|
|
if (undoStack.size() > UNDO_LIMIT) {
|
|
undoStack.remove(0);
|
|
}
|
|
}
|
|
else {
|
|
currentChange.append(e.text.length(), replacedText);
|
|
}
|
|
}
|
|
}
|
|
else if (!ignoreRedo) {
|
|
redoStack.push(new TextChange(e.start, e.text.length(), replacedText, text.getTopIndex(), text.getCaretOffset()));
|
|
if (redoStack.size() > UNDO_LIMIT) {
|
|
redoStack.remove(0);
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
text.addModifyListener(new ModifyListener() {
|
|
|
|
@Override
|
|
public void modifyText(ModifyEvent e) {
|
|
e.display.timerExec(500, refreshMarkersRunnable);
|
|
}
|
|
});
|
|
|
|
text.addLineStyleListener(new LineStyleListener() {
|
|
|
|
@Override
|
|
public void lineGetStyle(LineStyleEvent event) {
|
|
List<StyleRange> ranges = new ArrayList<StyleRange>();
|
|
|
|
Token token = tokenMarker.markTokens(event.lineText, event.lineOffset);
|
|
|
|
int offset = event.lineOffset;
|
|
while (token != null) {
|
|
TextStyle style = styleMap.get(token.id);
|
|
if (style != null) {
|
|
StyleRange range = new StyleRange(style);
|
|
range.start = offset;
|
|
range.length = token.length;
|
|
ranges.add(range);
|
|
}
|
|
offset += token.length;
|
|
token = token.next;
|
|
}
|
|
|
|
event.styles = ranges.toArray(new StyleRange[ranges.size()]);
|
|
}
|
|
});
|
|
|
|
text.addWordMovementListener(new MovementListener() {
|
|
|
|
@Override
|
|
public void getNextOffset(MovementEvent event) {
|
|
int offset = event.offset;
|
|
String lineText = text.getText();
|
|
if (offset < lineText.length()) {
|
|
if (event.movement == SWT.MOVEMENT_WORD_END) {
|
|
if (Character.isLetterOrDigit(lineText.charAt(offset)) || lineText.charAt(offset) == '_') {
|
|
do {
|
|
offset++;
|
|
if (offset >= lineText.length()) {
|
|
return;
|
|
}
|
|
} while (Character.isLetterOrDigit(lineText.charAt(offset)) || lineText.charAt(offset) == '_');
|
|
}
|
|
event.newOffset = offset;
|
|
return;
|
|
}
|
|
if (Character.isDigit(lineText.charAt(offset))) {
|
|
do {
|
|
offset++;
|
|
if (offset >= lineText.length()) {
|
|
return;
|
|
}
|
|
} while (Character.isDigit(lineText.charAt(offset)));
|
|
event.newOffset = offset;
|
|
return;
|
|
}
|
|
if (Character.isLetterOrDigit(lineText.charAt(offset))) {
|
|
offset++;
|
|
if (offset >= lineText.length()) {
|
|
return;
|
|
}
|
|
boolean lowerCase = Character.isLowerCase(lineText.charAt(offset));
|
|
do {
|
|
offset++;
|
|
if (offset >= lineText.length()) {
|
|
return;
|
|
}
|
|
} while (Character.isLetterOrDigit(lineText.charAt(offset)) && Character.isLowerCase(lineText.charAt(offset)) == lowerCase);
|
|
if (Character.isLetterOrDigit(lineText.charAt(offset))) {
|
|
event.newOffset = offset;
|
|
return;
|
|
}
|
|
if (lineText.charAt(offset) == '_') {
|
|
while (lineText.charAt(offset) == '_') {
|
|
offset++;
|
|
if (offset >= lineText.length()) {
|
|
return;
|
|
}
|
|
}
|
|
event.newOffset = offset;
|
|
return;
|
|
}
|
|
if (EditorUtil.isSeparator(lineText.charAt(offset))) {
|
|
event.newOffset = offset;
|
|
return;
|
|
}
|
|
}
|
|
if (EditorUtil.isWhitespace(lineText.charAt(offset))) {
|
|
do {
|
|
offset++;
|
|
if (offset >= lineText.length()) {
|
|
return;
|
|
}
|
|
} while (EditorUtil.isWhitespace(lineText.charAt(offset)));
|
|
event.newOffset = offset;
|
|
}
|
|
else if (EditorUtil.isSeparator(lineText.charAt(offset))) {
|
|
do {
|
|
offset++;
|
|
if (offset >= lineText.length()) {
|
|
return;
|
|
}
|
|
} while (EditorUtil.isSeparator(lineText.charAt(offset)));
|
|
if (EditorUtil.isWhitespace(lineText.charAt(offset))) {
|
|
do {
|
|
offset++;
|
|
if (offset >= lineText.length()) {
|
|
return;
|
|
}
|
|
} while (EditorUtil.isWhitespace(lineText.charAt(offset)));
|
|
}
|
|
event.newOffset = offset;
|
|
}
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void getPreviousOffset(MovementEvent event) {
|
|
int offset = event.offset;
|
|
String lineText = text.getText();
|
|
if (offset > 0 && offset < lineText.length()) {
|
|
offset--;
|
|
if (event.movement == SWT.MOVEMENT_WORD_START) {
|
|
if (Character.isLetterOrDigit(lineText.charAt(offset)) || lineText.charAt(offset) == '_') {
|
|
do {
|
|
offset--;
|
|
if (offset < 0) {
|
|
return;
|
|
}
|
|
} while (Character.isLetterOrDigit(lineText.charAt(offset)) || lineText.charAt(offset) == '_');
|
|
}
|
|
event.newOffset = offset + 1;
|
|
return;
|
|
}
|
|
if (EditorUtil.isWhitespace(lineText.charAt(offset))) {
|
|
do {
|
|
offset--;
|
|
if (offset < 0) {
|
|
return;
|
|
}
|
|
} while (EditorUtil.isWhitespace(lineText.charAt(offset)));
|
|
if (EditorUtil.isSeparator(lineText.charAt(offset))) {
|
|
while (offset > 0 && EditorUtil.isSeparator(lineText.charAt(offset - 1))) {
|
|
offset--;
|
|
}
|
|
event.newOffset = offset;
|
|
return;
|
|
}
|
|
}
|
|
if (Character.isDigit(lineText.charAt(offset))) {
|
|
do {
|
|
offset--;
|
|
if (offset < 0) {
|
|
return;
|
|
}
|
|
} while (Character.isDigit(lineText.charAt(offset)));
|
|
event.newOffset = offset + 1;
|
|
return;
|
|
}
|
|
while (lineText.charAt(offset) == '_') {
|
|
offset--;
|
|
if (offset < 0) {
|
|
return;
|
|
}
|
|
}
|
|
if (Character.isLetterOrDigit(lineText.charAt(offset))) {
|
|
if (Character.isLowerCase(lineText.charAt(offset))) {
|
|
do {
|
|
offset--;
|
|
if (offset < 0) {
|
|
return;
|
|
}
|
|
} while (Character.isLetterOrDigit(lineText.charAt(offset)) && Character.isLowerCase(lineText.charAt(offset)));
|
|
if (Character.isLetterOrDigit(lineText.charAt(offset))) {
|
|
event.newOffset = offset;
|
|
return;
|
|
}
|
|
}
|
|
else {
|
|
do {
|
|
offset--;
|
|
if (offset < 0) {
|
|
return;
|
|
}
|
|
} while (Character.isLetterOrDigit(lineText.charAt(offset)));
|
|
}
|
|
if (EditorUtil.isSeparator(lineText.charAt(offset)) || EditorUtil.isWhitespace(lineText.charAt(offset))) {
|
|
event.newOffset = offset + 1;
|
|
return;
|
|
}
|
|
}
|
|
if (EditorUtil.isSeparator(lineText.charAt(offset))) {
|
|
do {
|
|
offset--;
|
|
if (offset < 0) {
|
|
return;
|
|
}
|
|
} while (EditorUtil.isSeparator(lineText.charAt(offset)));
|
|
if (!EditorUtil.isSeparator(lineText.charAt(offset))) {
|
|
offset++;
|
|
}
|
|
event.newOffset = offset;
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
text.addDisposeListener(new DisposeListener() {
|
|
|
|
@Override
|
|
public void widgetDisposed(DisposeEvent e) {
|
|
if (searchBox != null) {
|
|
searchBox.dispose();
|
|
}
|
|
|
|
font.dispose();
|
|
fontBold.dispose();
|
|
|
|
for (TextStyle style : styleMap.values()) {
|
|
if (style.foreground != null) {
|
|
style.foreground.dispose();
|
|
}
|
|
}
|
|
currentLineBackground.dispose();
|
|
|
|
insertCaret.dispose();
|
|
overwriteCaret.dispose();
|
|
}
|
|
});
|
|
|
|
container.setTabList(new Control[] {
|
|
text
|
|
});
|
|
}
|
|
|
|
public Control getControl() {
|
|
return container;
|
|
}
|
|
|
|
public StyledText getStyledText() {
|
|
return text;
|
|
}
|
|
|
|
public void setText(String text) {
|
|
this.text.removeCaretListener(caretListener);
|
|
|
|
ignoreUndo = true;
|
|
ignoreRedo = true;
|
|
|
|
text = EditorUtil.trimLines(text);
|
|
text = EditorUtil.replaceTabs(text, this.text.getTabs());
|
|
|
|
currentLine = 0;
|
|
this.text.setText(text);
|
|
this.text.setLineBackground(currentLine, 1, currentLineBackground);
|
|
|
|
undoStack = new Stack<TextChange>();
|
|
redoStack = new Stack<TextChange>();
|
|
ignoreUndo = false;
|
|
ignoreRedo = false;
|
|
|
|
this.text.addCaretListener(caretListener);
|
|
}
|
|
|
|
public void replaceText(String text) {
|
|
int offset = this.text.getCaretOffset();
|
|
int line = this.text.getLineAtOffset(offset);
|
|
int topindex = line - this.text.getTopIndex();
|
|
|
|
text = EditorUtil.trimLines(text);
|
|
text = EditorUtil.replaceTabs(text, this.text.getTabs());
|
|
|
|
this.text.setText(text);
|
|
|
|
if (line > this.text.getLineCount()) {
|
|
line = this.text.getLineCount() - 1;
|
|
}
|
|
this.text.setTopIndex(line - topindex);
|
|
this.text.setCaretOffset(this.text.getOffsetAtLine(line));
|
|
}
|
|
|
|
public String getText() {
|
|
String s = text.getText();
|
|
String s2 = EditorUtil.trimLines(s);
|
|
if (!s2.equals(s)) {
|
|
int offset = text.getCaretOffset();
|
|
int line = text.getLineAtOffset(offset);
|
|
int column = offset - text.getOffsetAtLine(line);
|
|
int topindex = text.getTopIndex();
|
|
|
|
text.setRedraw(false);
|
|
try {
|
|
text.setText(s2);
|
|
text.setTopIndex(topindex);
|
|
|
|
String ls = text.getLine(line);
|
|
text.setCaretOffset(text.getOffsetAtLine(line) + Math.min(column, ls.length()));
|
|
} finally {
|
|
text.setRedraw(true);
|
|
}
|
|
}
|
|
return s2;
|
|
}
|
|
|
|
private Caret createInsertCaret(StyledText styledText) {
|
|
Caret caret = new Caret(styledText, SWT.NULL);
|
|
caret.setSize(2, styledText.getLineHeight());
|
|
caret.setFont(styledText.getFont());
|
|
return caret;
|
|
}
|
|
|
|
private Caret createOverwriteCaret(StyledText styledText) {
|
|
Caret caret = new Caret(styledText, SWT.NULL);
|
|
|
|
GC gc = new GC(styledText);
|
|
Point charSize = gc.stringExtent("a"); //$NON-NLS-1$
|
|
|
|
caret.setSize(charSize.x, styledText.getLineHeight());
|
|
caret.setFont(styledText.getFont());
|
|
|
|
gc.dispose();
|
|
|
|
return caret;
|
|
}
|
|
|
|
void handleAutoIndent() {
|
|
int i;
|
|
int offset = text.getCaretOffset();
|
|
int lineIndex = text.getLineAtOffset(offset);
|
|
String lineText = text.getLine(lineIndex);
|
|
int lineOffset = offset - text.getOffsetAtLine(lineIndex);
|
|
int index = 0;
|
|
StringBuilder sb = new StringBuilder();
|
|
|
|
i = 0;
|
|
while (i < lineText.length() && (lineText.charAt(i) == ' ' || lineText.charAt(i) == '\t')) {
|
|
sb.append(' ');
|
|
i++;
|
|
}
|
|
|
|
i = lineOffset;
|
|
while (i < lineText.length() && (lineText.charAt(i) == ' ' || lineText.charAt(i) == '\t') && index < sb.length()) {
|
|
index++;
|
|
i++;
|
|
}
|
|
sb.insert(index, text.getLineDelimiter());
|
|
|
|
text.setRedraw(false);
|
|
try {
|
|
text.insert(sb.toString());
|
|
text.setCaretOffset(offset + sb.length());
|
|
} finally {
|
|
text.setRedraw(true);
|
|
}
|
|
}
|
|
|
|
public void clearKeywords() {
|
|
tokenMarker.clearKeywords();
|
|
}
|
|
|
|
public void addKeyword(String text, TokenId id) {
|
|
tokenMarker.addKeyword(text, id);
|
|
}
|
|
|
|
public void removeKeyword(String text) {
|
|
tokenMarker.removeKeyword(text);
|
|
}
|
|
|
|
public void undo() {
|
|
if (undoStack.empty()) {
|
|
return;
|
|
}
|
|
|
|
TextChange change = undoStack.pop();
|
|
|
|
ignoreUndo = true;
|
|
try {
|
|
text.setRedraw(false);
|
|
text.replaceTextRange(change.start, change.length, change.replacedText);
|
|
text.setCaretOffset(change.caretOffset);
|
|
text.setTopIndex(change.topIndex);
|
|
} finally {
|
|
text.setRedraw(true);
|
|
ignoreUndo = false;
|
|
}
|
|
}
|
|
|
|
public void redo() {
|
|
if (redoStack.empty()) {
|
|
return;
|
|
}
|
|
|
|
TextChange change = redoStack.pop();
|
|
|
|
ignoreRedo = true;
|
|
try {
|
|
text.setRedraw(false);
|
|
text.replaceTextRange(change.start, change.length, change.replacedText);
|
|
text.setCaretOffset(change.caretOffset);
|
|
text.setTopIndex(change.topIndex);
|
|
} finally {
|
|
text.setRedraw(true);
|
|
ignoreRedo = false;
|
|
}
|
|
}
|
|
|
|
void goToLine() {
|
|
IInputValidator validator = new IInputValidator() {
|
|
|
|
@Override
|
|
public String isValid(String newText) {
|
|
try {
|
|
int i = Integer.parseInt(newText);
|
|
if (i < 1 || i > text.getLineCount()) {
|
|
return "Line number out of range";
|
|
}
|
|
} catch (Exception e) {
|
|
return "Not a number";
|
|
}
|
|
return null;
|
|
}
|
|
};
|
|
String msg = String.format("Enter line number (1..%d):", text.getLineCount());
|
|
int line = text.getLineAtOffset(text.getCaretOffset());
|
|
InputDialog dlg = new InputDialog(text.getShell(), "Go to Line", msg, String.format("%d", line), validator);
|
|
if (dlg.open() == InputDialog.OK) {
|
|
line = Integer.parseInt(dlg.getValue()) - 1;
|
|
text.setCaretOffset(text.getOffsetAtLine(line));
|
|
text.setTopIndex(line > 10 ? line - 10 : 1);
|
|
}
|
|
}
|
|
|
|
public void cut() {
|
|
text.cut();
|
|
}
|
|
|
|
public void copy() {
|
|
text.copy();
|
|
}
|
|
|
|
public void paste() {
|
|
final VerifyListener verifyListener = new VerifyListener() {
|
|
|
|
@Override
|
|
public void verifyText(VerifyEvent e) {
|
|
if (e.text != null) {
|
|
e.text = EditorUtil.replaceTabs(e.text, text.getTabs());
|
|
}
|
|
}
|
|
};
|
|
text.addVerifyListener(verifyListener);
|
|
text.paste();
|
|
text.removeVerifyListener(verifyListener);
|
|
}
|
|
|
|
public void selectAll() {
|
|
text.selectAll();
|
|
}
|
|
|
|
public void addModifyListener(ModifyListener listener) {
|
|
text.addModifyListener(listener);
|
|
}
|
|
|
|
public void removeModifyListener(ModifyListener listener) {
|
|
text.removeModifyListener(listener);
|
|
}
|
|
|
|
public void addCaretListener(CaretListener listener) {
|
|
text.addCaretListener(listener);
|
|
}
|
|
|
|
public void removeCaretListener(CaretListener listener) {
|
|
text.removeCaretListener(listener);
|
|
}
|
|
|
|
public void gotToLineColumn(int line, int column) {
|
|
if (line >= text.getLineCount()) {
|
|
return;
|
|
}
|
|
|
|
Rectangle rect = text.getClientArea();
|
|
int bottomIndex = text.getLineIndex(rect.height - text.getLineHeight());
|
|
if (line <= text.getTopIndex() || line >= bottomIndex) {
|
|
text.setTopIndex(line > 10 ? line - 10 : 1);
|
|
}
|
|
|
|
int offset = text.getOffsetAtLine(line);
|
|
text.setCaretOffset(offset + column);
|
|
|
|
text.showSelection();
|
|
|
|
ruler.redraw();
|
|
}
|
|
|
|
public boolean isShowLineNumbers() {
|
|
return showLineNumbers;
|
|
}
|
|
|
|
public void setShowLineNumbers(boolean showLineNumbers) {
|
|
if (this.showLineNumbers == showLineNumbers) {
|
|
return;
|
|
}
|
|
this.showLineNumbers = showLineNumbers;
|
|
|
|
ruler.setVisible(showLineNumbers);
|
|
|
|
container.getDisplay().asyncExec(new Runnable() {
|
|
|
|
@Override
|
|
public void run() {
|
|
if (container.isDisposed()) {
|
|
return;
|
|
}
|
|
container.layout();
|
|
text.redraw();
|
|
text.setCaretOffset(text.getCaretOffset());
|
|
}
|
|
});
|
|
}
|
|
|
|
public void toggleLineNumbers() {
|
|
setShowLineNumbers(!showLineNumbers);
|
|
}
|
|
|
|
public void setFont(String name) {
|
|
if (name == null || "".equals(name)) {
|
|
if ("win32".equals(SWT.getPlatform())) {
|
|
name = StringConverter.asString(new FontData("Courier New", 9, SWT.NONE));
|
|
}
|
|
else {
|
|
name = StringConverter.asString(new FontData("mono", 9, SWT.NONE));
|
|
}
|
|
}
|
|
FontData fontData = StringConverter.asFontData(name);
|
|
setFont(fontData.getName(), fontData.getHeight());
|
|
}
|
|
|
|
public void setFont(String name, int size) {
|
|
Font oldFont = font;
|
|
Font oldFontBold = fontBold;
|
|
|
|
font = new Font(Display.getDefault(), name, size, SWT.NONE);
|
|
fontBold = new Font(Display.getDefault(), name, size, SWT.BOLD);
|
|
|
|
updateTokenStyles();
|
|
text.setStyleRanges(new StyleRange[0]);
|
|
text.setFont(font);
|
|
|
|
text.redraw();
|
|
text.setCaretOffset(text.getCaretOffset());
|
|
|
|
ruler.setFont(font);
|
|
|
|
container.getDisplay().asyncExec(new Runnable() {
|
|
|
|
@Override
|
|
public void run() {
|
|
if (!container.isDisposed()) {
|
|
container.layout();
|
|
}
|
|
}
|
|
});
|
|
|
|
if (oldFont != null) {
|
|
oldFont.dispose();
|
|
}
|
|
if (oldFontBold != null) {
|
|
oldFontBold.dispose();
|
|
}
|
|
}
|
|
|
|
public void setDefaultFont() {
|
|
if ("win32".equals(SWT.getPlatform())) {
|
|
setFont("Courier New", 9);
|
|
}
|
|
else {
|
|
setFont("mono", 9);
|
|
}
|
|
}
|
|
|
|
void updateTokenStyles() {
|
|
styleMap.put(TokenId.Comment, new TextStyle(font, new Color(Display.getDefault(), 0x00, 0x00, 0xFF), null));
|
|
|
|
styleMap.put(TokenId.Directive1, new TextStyle(font, new Color(Display.getDefault(), 0xA0, 0x20, 0xF0), null));
|
|
styleMap.put(TokenId.Directive2, new TextStyle(fontBold, new Color(Display.getDefault(), 0x2E, 0x8B, 0x57), null));
|
|
|
|
styleMap.put(TokenId.Instruction, new TextStyle(fontBold, new Color(Display.getDefault(), 0xA5, 0x2A, 0x2A), null));
|
|
styleMap.put(TokenId.Flag, new TextStyle(fontBold, new Color(Display.getDefault(), 0xA5, 0x2A, 0x2A), null));
|
|
styleMap.put(TokenId.Register, new TextStyle(fontBold, new Color(Display.getDefault(), 0xA5, 0x2A, 0x2A), null));
|
|
|
|
styleMap.put(TokenId.StringLiteral1, new TextStyle(font, new Color(Display.getDefault(), 0xFF, 0x00, 0xFF), null));
|
|
styleMap.put(TokenId.StringLiteral2, new TextStyle(font, new Color(Display.getDefault(), 0xFF, 0x00, 0xFF), null));
|
|
styleMap.put(TokenId.NumberLiteral, new TextStyle(font, new Color(Display.getDefault(), 0xFF, 0x00, 0xFF), null));
|
|
}
|
|
|
|
void doLineStart(boolean doSelect) {
|
|
int offset = text.getCaretOffset();
|
|
int lineNumber = text.getLineAtOffset(offset);
|
|
int lineOffset = offset - text.getOffsetAtLine(lineNumber);
|
|
String line = text.getLine(lineNumber);
|
|
int nonBlankOffset = 0;
|
|
while (nonBlankOffset < line.length() && (line.charAt(nonBlankOffset) == ' ' || line.charAt(nonBlankOffset) == '\t')) {
|
|
nonBlankOffset++;
|
|
}
|
|
if (lineOffset == nonBlankOffset) {
|
|
lineOffset = 0;
|
|
}
|
|
else {
|
|
lineOffset = nonBlankOffset;
|
|
}
|
|
|
|
text.setRedraw(false);
|
|
try {
|
|
int newOffset = lineOffset + text.getOffsetAtLine(lineNumber);
|
|
if (doSelect) {
|
|
Point selection = text.getSelection();
|
|
if (offset == selection.x) {
|
|
text.setSelection(selection.y, newOffset);
|
|
}
|
|
else if (offset == selection.y) {
|
|
text.setSelection(selection.x, newOffset);
|
|
}
|
|
else {
|
|
text.setSelection(offset, newOffset);
|
|
}
|
|
}
|
|
text.setCaretOffset(newOffset);
|
|
text.setHorizontalIndex(0);
|
|
text.showSelection();
|
|
} finally {
|
|
text.setRedraw(true);
|
|
}
|
|
}
|
|
|
|
void doLineEnd(boolean doSelect) {
|
|
int offset = text.getCaretOffset();
|
|
int lineNumber = text.getLineAtOffset(offset);
|
|
int lineOffset = offset - text.getOffsetAtLine(lineNumber);
|
|
String line = text.getLine(lineNumber);
|
|
int nonBlankOffset = line.length();
|
|
while (nonBlankOffset > 0 && (line.charAt(nonBlankOffset - 1) == ' ' || line.charAt(nonBlankOffset - 1) == '\t')) {
|
|
nonBlankOffset--;
|
|
}
|
|
if (lineOffset == nonBlankOffset) {
|
|
lineOffset = line.length();
|
|
}
|
|
else {
|
|
lineOffset = nonBlankOffset;
|
|
}
|
|
|
|
text.setRedraw(false);
|
|
try {
|
|
int newOffset = lineOffset + text.getOffsetAtLine(lineNumber);
|
|
if (doSelect) {
|
|
Point selection = text.getSelection();
|
|
if (offset == selection.x) {
|
|
text.setSelection(selection.y, newOffset);
|
|
}
|
|
else if (offset == selection.y) {
|
|
text.setSelection(selection.x, newOffset);
|
|
}
|
|
else {
|
|
text.setSelection(offset, newOffset);
|
|
}
|
|
}
|
|
text.setCaretOffset(newOffset);
|
|
text.showSelection();
|
|
} finally {
|
|
text.setRedraw(true);
|
|
}
|
|
}
|
|
|
|
void doTab() {
|
|
int offset = text.getCaretOffset();
|
|
int lineNumber = text.getLineAtOffset(offset);
|
|
int lineStart = text.getOffsetAtLine(lineNumber);
|
|
int lineOffset = offset - lineStart;
|
|
|
|
int tabStop = ((lineOffset + text.getTabs()) / text.getTabs()) * text.getTabs();
|
|
if (useTabstops && tabStops != null) {
|
|
for (int i = tabStops.length - 1; i >= 0; i--) {
|
|
if (lineOffset < tabStops[i]) {
|
|
tabStop = tabStops[i];
|
|
}
|
|
}
|
|
}
|
|
|
|
StringBuilder sb = new StringBuilder();
|
|
while (sb.length() < (tabStop - lineOffset)) {
|
|
sb.append(' ');
|
|
}
|
|
|
|
text.setRedraw(false);
|
|
try {
|
|
text.insert(sb.toString());
|
|
text.setCaretOffset(lineStart + tabStop);
|
|
text.showSelection();
|
|
} finally {
|
|
text.setRedraw(true);
|
|
}
|
|
}
|
|
|
|
boolean doBackspace() {
|
|
int offset = text.getCaretOffset();
|
|
int lineNumber = text.getLineAtOffset(offset);
|
|
int lineStart = text.getOffsetAtLine(lineNumber);
|
|
|
|
if (offset == lineStart || !useTabstops) {
|
|
return true;
|
|
}
|
|
|
|
int lineOffset = offset - lineStart;
|
|
|
|
int tabStop = (lineOffset / text.getTabs()) * text.getTabs();
|
|
if (tabStop == lineOffset) {
|
|
tabStop -= text.getTabs();
|
|
}
|
|
|
|
if (tabStops != null) {
|
|
for (int i = 0; i < tabStops.length; i++) {
|
|
if (tabStops[i] < lineOffset) {
|
|
tabStop = tabStops[i];
|
|
}
|
|
}
|
|
}
|
|
|
|
String s = text.getLine(lineNumber).substring(tabStop, lineOffset);
|
|
while (s.endsWith(" ")) {
|
|
s = s.substring(0, s.length() - 1);
|
|
}
|
|
if (s.length() != 0) {
|
|
s = s + " ";
|
|
if ((s.length() + tabStop) >= lineOffset) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
text.setRedraw(false);
|
|
try {
|
|
text.replaceTextRange(lineStart + tabStop, lineOffset - tabStop, s);
|
|
text.showSelection();
|
|
} finally {
|
|
text.setRedraw(true);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
public int[] getTabStops() {
|
|
return tabStops;
|
|
}
|
|
|
|
public void setTabStops(int[] tabStops) {
|
|
this.tabStops = new int[tabStops.length + 1];
|
|
this.tabStops[0] = 0;
|
|
System.arraycopy(tabStops, 0, this.tabStops, 1, tabStops.length);
|
|
}
|
|
|
|
public boolean isUseTabstops() {
|
|
return useTabstops;
|
|
}
|
|
|
|
public void setUseTabstops(boolean useTabstops) {
|
|
this.useTabstops = useTabstops;
|
|
}
|
|
|
|
public void setTabWidth(int width) {
|
|
text.setTabs(width);
|
|
}
|
|
|
|
public int getTabWidth() {
|
|
return text.getTabs();
|
|
}
|
|
|
|
}
|