kopia lustrzana https://github.com/maccasoft/z80-tools
440 wiersze
14 KiB
Java
440 wiersze
14 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;
|
|
|
|
import java.util.ArrayList;
|
|
import java.util.HashMap;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
|
|
import org.eclipse.jface.dialogs.IInputValidator;
|
|
import org.eclipse.jface.dialogs.InputDialog;
|
|
import org.eclipse.swt.SWT;
|
|
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.StyleRange;
|
|
import org.eclipse.swt.custom.StyledText;
|
|
import org.eclipse.swt.events.DisposeEvent;
|
|
import org.eclipse.swt.events.DisposeListener;
|
|
import org.eclipse.swt.events.ModifyListener;
|
|
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.Rectangle;
|
|
import org.eclipse.swt.graphics.TextStyle;
|
|
import org.eclipse.swt.layout.GridData;
|
|
import org.eclipse.swt.layout.GridLayout;
|
|
import org.eclipse.swt.widgets.Composite;
|
|
import org.eclipse.swt.widgets.Control;
|
|
import org.eclipse.swt.widgets.Display;
|
|
|
|
import com.maccasoft.tools.SourceMap.LineEntry;
|
|
import com.maccasoft.tools.editor.EditorUtil;
|
|
import com.maccasoft.tools.editor.LineNumbersRuler;
|
|
import com.maccasoft.tools.editor.Token;
|
|
import com.maccasoft.tools.editor.TokenId;
|
|
import com.maccasoft.tools.editor.TokenMarker;
|
|
|
|
public class SourceViewer {
|
|
|
|
Composite container;
|
|
LineNumbersRuler ruler;
|
|
CodeRuler codeRuler;
|
|
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>();
|
|
|
|
SourceMap sourceMap;
|
|
|
|
boolean showLineNumbers = true;
|
|
boolean highlighCurrentLine = true;
|
|
|
|
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, highlighCurrentLine ? currentLineBackground : null);
|
|
currentLine = line;
|
|
}
|
|
}
|
|
};
|
|
|
|
public SourceViewer(Composite parent, TokenMarker tokenMarker) {
|
|
this.tokenMarker = tokenMarker;
|
|
createTextEditor(parent);
|
|
}
|
|
|
|
protected void createTextEditor(Composite parent) {
|
|
container = new Composite(parent, SWT.BORDER);
|
|
GridLayout containerLayout = new GridLayout(3, 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);
|
|
|
|
codeRuler = new CodeRuler(container);
|
|
|
|
text = new StyledText(container, SWT.FULL_SELECTION | SWT.H_SCROLL | SWT.V_SCROLL);
|
|
text.setEditable(false);
|
|
text.setDoubleClickEnabled(false);
|
|
text.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
|
|
text.setMargins(5, 5, 5, 5);
|
|
text.setTabs(4);
|
|
text.setFont(font);
|
|
text.setCaret(null);
|
|
|
|
ruler.setFont(font);
|
|
ruler.setText(text);
|
|
|
|
codeRuler.setFont(font, fontBold);
|
|
codeRuler.setText(text);
|
|
|
|
currentLine = 0;
|
|
currentLineBackground = new Color(Display.getDefault(), 232, 242, 254);
|
|
text.setLineBackground(currentLine, 1, highlighCurrentLine ? currentLineBackground : null);
|
|
|
|
updateTokenStyles();
|
|
|
|
text.addCaretListener(caretListener);
|
|
|
|
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.addDisposeListener(new DisposeListener() {
|
|
|
|
@Override
|
|
public void widgetDisposed(DisposeEvent e) {
|
|
font.dispose();
|
|
fontBold.dispose();
|
|
for (TextStyle style : styleMap.values()) {
|
|
if (style.foreground != null) {
|
|
style.foreground.dispose();
|
|
}
|
|
}
|
|
currentLineBackground.dispose();
|
|
}
|
|
});
|
|
|
|
container.setTabList(new Control[] {
|
|
text
|
|
});
|
|
}
|
|
|
|
public Control getControl() {
|
|
return container;
|
|
}
|
|
|
|
public StyledText getStyledText() {
|
|
return text;
|
|
}
|
|
|
|
public SourceMap getSourceMap() {
|
|
return sourceMap;
|
|
}
|
|
|
|
public void setSourceMap(SourceMap sourceMap) {
|
|
this.sourceMap = sourceMap;
|
|
|
|
StringBuilder sb = new StringBuilder();
|
|
for (LineEntry lineEntry : sourceMap.getLines()) {
|
|
sb.append(lineEntry.line.getSourceText());
|
|
sb.append("\r\n");
|
|
}
|
|
String text = sb.toString();
|
|
|
|
codeRuler.setSourceMap(sourceMap);
|
|
|
|
currentLine = 0;
|
|
this.text.setText(text);
|
|
this.text.setLineBackground(currentLine, 1, highlighCurrentLine ? currentLineBackground : null);
|
|
tokenMarker.refreshMultilineComments(text);
|
|
}
|
|
|
|
public String getText() {
|
|
String s = text.getText();
|
|
String s2 = s.replaceAll("[ \\t]+(\r\n|\n|\r)", "$1");
|
|
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;
|
|
}
|
|
|
|
public void clearKeywords() {
|
|
tokenMarker.clearKeywords();
|
|
}
|
|
|
|
public void addKeyword(String text, TokenId id) {
|
|
tokenMarker.addKeyword(text, id);
|
|
}
|
|
|
|
public void removeKeyword(String text) {
|
|
tokenMarker.removeKeyword(text);
|
|
}
|
|
|
|
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();
|
|
codeRuler.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, 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);
|
|
codeRuler.setFont(font, fontBold);
|
|
|
|
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));
|
|
}
|
|
|
|
public void toggleBreakpoint(int address) {
|
|
codeRuler.toggleBreakpoint(address);
|
|
codeRuler.redraw();
|
|
}
|
|
|
|
public boolean isBreakpoint(int address) {
|
|
return codeRuler.isBreakpoint(address);
|
|
}
|
|
|
|
public void resetBreakpoints() {
|
|
codeRuler.resetBreakpoints();
|
|
codeRuler.redraw();
|
|
}
|
|
|
|
public void setHighlighCurrentLine(boolean highlighCurrentLine) {
|
|
this.highlighCurrentLine = highlighCurrentLine;
|
|
text.setLineBackground(currentLine, 1, highlighCurrentLine ? currentLineBackground : null);
|
|
}
|
|
|
|
}
|