/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ro.nextreports.designer.ui.sqleditor.syntax;
import java.awt.event.ActionEvent;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import javax.swing.Action;
import javax.swing.JEditorPane;
import javax.swing.KeyStroke;
import javax.swing.text.DefaultEditorKit;
import javax.swing.text.Document;
import javax.swing.text.Element;
import javax.swing.text.JTextComponent;
import javax.swing.text.Keymap;
import javax.swing.text.PlainDocument;
import javax.swing.text.TextAction;
import javax.swing.text.View;
import javax.swing.text.ViewFactory;
/**
* To use the package, just set the EditorKit of the EditorPane to a new
* instance of this class. You need to pass a proper lexer to the class.
*
* @author Decebal Suiu
*/
public class SyntaxEditorKit extends DefaultEditorKit implements ViewFactory {
/** Undo action. */
public static final String undoAction = "undo";
/** Redo action. */
public static final String redoAction = "redo";
/** Indent action. */
public static final String indentAction = "indent";
/** Unindent action. */
public static final String unindentAction = "unindent";
/** Smart indent action. */
public static final String smartIndentAction = "smart-indent";
private static final long serialVersionUID = 3971907941600240991L;
private Lexer lexer;
/**
* Create a new Kit for the given language
*/
public SyntaxEditorKit(Lexer lexer) {
super();
this.lexer = lexer;
}
@Override
public ViewFactory getViewFactory() {
return this;
}
public View create(Element element) {
return new SyntaxView(element);
}
/**
* This is called by Swing to create a Document for the JEditorPane document
* This may be called before you actually get a reference to the control.
* We use it here to create a properl lexer and pass it to the
* SyntaxDcument we return.
* @return
*/
@Override
public Document createDefaultDocument() {
return new SyntaxDocument(lexer);
}
@Override
public void install(JEditorPane target) {
super.install(target);
// add key bindings
Keymap keymap = target.getKeymap();
JTextComponent.loadKeymap(keymap, getKeyBindings(), target.getActions());
}
/**
* Get actions associated with this kit. getCustomActions() is called
* to get basic list.
*/
public final Action[] getActions() {
List<Action> actions = new ArrayList<Action>();
actions.addAll(Arrays.asList(super.getActions()));
actions.addAll(Arrays.asList(getDefaultActions()));
actions.addAll(Arrays.asList(getCustomActions()));
return actions.toArray(new Action[actions.size()]);
}
protected Action[] getCustomActions() {
return new Action[0];
}
private Action[] getDefaultActions() {
return new Action[] {
new UndoAction(),
new RedoAction(),
new IndentAction(),
new UnindentAction()
};
}
protected JTextComponent.KeyBinding[] getKeyBindings() {
List<JTextComponent.KeyBinding> keyBindings = new ArrayList<JTextComponent.KeyBinding>();
keyBindings.addAll(Arrays.asList(getDefaultKeyBindings()));
keyBindings.addAll(Arrays.asList(getCustomKeyBindings()));
return keyBindings.toArray(new JTextComponent.KeyBinding[keyBindings.size()]);
}
protected JTextComponent.KeyBinding[] getCustomKeyBindings() {
return new JTextComponent.KeyBinding[0];
}
private JTextComponent.KeyBinding[] getDefaultKeyBindings() {
return new JTextComponent.KeyBinding[] {
new JTextComponent.KeyBinding(KeyStroke.getKeyStroke("control Z"), undoAction),
new JTextComponent.KeyBinding(KeyStroke.getKeyStroke("control Y"), redoAction),
new JTextComponent.KeyBinding(KeyStroke.getKeyStroke("TAB"), indentAction),
new JTextComponent.KeyBinding(KeyStroke.getKeyStroke("shift TAB"), unindentAction)
};
}
/**
* A Pair action inserts a pair of characters (left and right) around the
* current selection, and then places the caret between them.
*/
public static class PairAction extends TextAction {
private static final long serialVersionUID = 512435731659762179L;
protected String left;
protected String right;
public PairAction(String actionName, String left, String right) {
super(actionName);
this.left = left;
this.right = right;
}
public void actionPerformed(ActionEvent event) {
JTextComponent target = getTextComponent(event);
if (target != null) {
String selected = target.getSelectedText();
if (selected != null) {
target.replaceSelection(left + selected + right);
} else {
target.replaceSelection(left + right);
}
target.setCaretPosition(target.getCaretPosition() - 1);
}
}
}
/**
* This action performs SmartIndentation each time VK_ENTER is pressed
* SmartIndentation is inserting the same amount of spaces as
* the line above. May not be too smart, but good enough.
*/
public static class SmartIndentAction extends TextAction {
private static final long serialVersionUID = -4884630822418253474L;
public SmartIndentAction() {
super(smartIndentAction);
}
public void actionPerformed(ActionEvent event) {
JTextComponent target = getTextComponent(event);
if (target != null) {
String line = SyntaxUtil.getLine(target);
/**
* Perform Smart Indentation: pos must be on a line: this method will
* use the previous lines indentation (number of spaces before any non-space
* character or end of line) and return that as the prefix.
*/
String indent = "";
if (line != null && line.length() > 0) {
int i = 0;
while (i < line.length() && line.charAt(i) == ' ') {
i++;
}
indent = line.substring(0, i);
}
target.replaceSelection("\n" + indent);
}
}
}
/**
* IndentAction is used to replace Tabs with spaces. If there is selected
* text, then the lines spanning the selection will be shifted
* right by one tab-width space character.
*/
public static class IndentAction extends DefaultEditorKit.InsertTabAction {
private static final long serialVersionUID = 182313664992032966L;
public IndentAction() {
super();
putValue(Action.NAME, indentAction);
}
@Override
public void actionPerformed(ActionEvent event) {
JTextComponent target = getTextComponent(event);
if (target != null) {
String selected = target.getSelectedText();
if (selected == null) {
PlainDocument pDoc = (PlainDocument) target.getDocument();
Integer tabStop = (Integer) pDoc.getProperty(PlainDocument.tabSizeAttribute);
// insert needed number of tabs:
int lineStart = pDoc.getParagraphElement(target.getCaretPosition()).getStartOffset();
// column
int column = target.getCaretPosition() - lineStart;
int needed = tabStop - (column % tabStop);
target.replaceSelection(SyntaxUtil.SPACES.substring(0, needed));
} else {
String[] lines = SyntaxUtil.getSelectedLines(target);
int start = target.getSelectionStart();
StringBuilder sb = new StringBuilder();
for (String line : lines) {
sb.append('\t');
sb.append(line);
sb.append('\n');
}
target.replaceSelection(sb.toString());
target.select(start, start + sb.length());
}
}
}
}
/**
* This is usually mapped to Shift-TAB to unindent the selection. The
* current line, or the selected lines are un-indented by the tabstop of the
* document.
*/
public static class UnindentAction extends TextAction {
private static final long serialVersionUID = -4364953875980816216L;
public UnindentAction() {
super(unindentAction);
}
public void actionPerformed(ActionEvent event) {
JTextComponent target = getTextComponent(event);
Integer tabStop = (Integer) target.getDocument().getProperty(PlainDocument.tabSizeAttribute);
String indent = SyntaxUtil.SPACES.substring(0, tabStop);
if (target != null) {
String[] lines = SyntaxUtil.getSelectedLines(target);
int start = target.getSelectionStart();
StringBuilder sb = new StringBuilder();
for (String line : lines) {
if (line.startsWith(indent)) {
sb.append(line.substring(indent.length()));
} else if (line.startsWith("\t")) {
sb.append(line.substring(1));
} else {
sb.append(line);
}
sb.append('\n');
}
target.replaceSelection(sb.toString());
target.select(start, start + sb.length());
}
}
}
/**
* Undo action.
*/
public static class UndoAction extends TextAction {
private static final long serialVersionUID = -1872733699834542335L;
public UndoAction() {
super(undoAction);
}
public void actionPerformed(ActionEvent event) {
JTextComponent target = getTextComponent(event);
if (target != null) {
if (target.getDocument() instanceof SyntaxDocument) {
SyntaxDocument document = (SyntaxDocument) target.getDocument();
document.doUndo();
}
}
}
}
/**
* Redo action.
*/
public static class RedoAction extends TextAction {
private static final long serialVersionUID = 5474351789865934556L;
public RedoAction() {
super(redoAction);
}
public void actionPerformed(ActionEvent event) {
JTextComponent target = getTextComponent(event);
if (target != null) {
if (target.getDocument() instanceof SyntaxDocument) {
SyntaxDocument document = (SyntaxDocument) target.getDocument();
document.doRedo();
}
}
}
}
}