mirror of
https://github.com/SuspiciousActivity/ByteEdit.git
synced 2026-08-13 17:56:17 +00:00
Added source for ByteEdit
This commit is contained in:
1 parent
32dacb30ff
commit
d29e5765af
352 files changed
+199016
No files matched your search
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,441 @@
|
|||||||
|
package me.ByteEdit.main;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.StringEscapeUtils;
|
||||||
|
|
||||||
|
public class ClassUtil {
|
||||||
|
|
||||||
|
public static final int ACC_PUBLIC = 0x0001;
|
||||||
|
public static final int ACC_PRIVATE = 0x0002;
|
||||||
|
public static final int ACC_PROTECTED = 0x0004;
|
||||||
|
public static final int ACC_STATIC = 0x0008;
|
||||||
|
public static final int ACC_FINAL = 0x0010;
|
||||||
|
public static final int ACC_SYNCHRONIZED = 0x0020;
|
||||||
|
public static final int ACC_SUPER = 0x0020;
|
||||||
|
public static final int ACC_BRIDGE = 0x0040;
|
||||||
|
public static final int ACC_VARARGS = 0x0080;
|
||||||
|
public static final int ACC_NATIVE = 0x0100;
|
||||||
|
public static final int ACC_INTERFACE = 0x0200;
|
||||||
|
public static final int ACC_ABSTRACT = 0x0400;
|
||||||
|
public static final int ACC_STRICTFP = 0x0800;
|
||||||
|
public static final int ACC_SYNTHETIC = 0x1000;
|
||||||
|
public static final int ACC_ANNOTATION = 0x2000;
|
||||||
|
public static final int ACC_ENUM = 0x4000;
|
||||||
|
public static final int ACC_MANDATED = 0x8000;
|
||||||
|
|
||||||
|
public static String getAccessFlagsClass(int access) {
|
||||||
|
String s = "";
|
||||||
|
if ((ACC_PUBLIC & access) != 0) {
|
||||||
|
s += "public ";
|
||||||
|
}
|
||||||
|
if ((ACC_FINAL & access) != 0) {
|
||||||
|
s += "final ";
|
||||||
|
}
|
||||||
|
if ((ACC_ABSTRACT & access) != 0 && (ACC_ANNOTATION & access) == 0 && (ACC_ENUM & access) == 0
|
||||||
|
&& (ACC_INTERFACE & access) == 0) {
|
||||||
|
s += "abstract ";
|
||||||
|
}
|
||||||
|
if ((ACC_SYNTHETIC & access) != 0) {
|
||||||
|
s += "synthetic ";
|
||||||
|
}
|
||||||
|
if ((ACC_ANNOTATION & access) != 0) {
|
||||||
|
s += "@interface ";
|
||||||
|
} else if ((ACC_INTERFACE & access) != 0) {
|
||||||
|
s += "interface ";
|
||||||
|
} else if ((ACC_ENUM & access) != 0) {
|
||||||
|
s += "enum ";
|
||||||
|
} else {
|
||||||
|
s += "class ";
|
||||||
|
}
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getAccessFlagsFull(int access) {
|
||||||
|
String s = "";
|
||||||
|
if ((ACC_PUBLIC & access) != 0) {
|
||||||
|
s += "public ";
|
||||||
|
}
|
||||||
|
if ((ACC_PRIVATE & access) != 0) {
|
||||||
|
s += "private ";
|
||||||
|
}
|
||||||
|
if ((ACC_PROTECTED & access) != 0) {
|
||||||
|
s += "protected ";
|
||||||
|
}
|
||||||
|
if ((ACC_STATIC & access) != 0) {
|
||||||
|
s += "static ";
|
||||||
|
}
|
||||||
|
if ((ACC_FINAL & access) != 0) {
|
||||||
|
s += "final ";
|
||||||
|
}
|
||||||
|
if ((ACC_SYNCHRONIZED & access) != 0) {
|
||||||
|
s += "synchronized ";
|
||||||
|
}
|
||||||
|
if ((ACC_VARARGS & access) != 0) {
|
||||||
|
s += "varargs ";
|
||||||
|
}
|
||||||
|
if ((ACC_NATIVE & access) != 0) {
|
||||||
|
s += "native ";
|
||||||
|
}
|
||||||
|
if ((ACC_INTERFACE & access) != 0) {
|
||||||
|
s += "interface ";
|
||||||
|
}
|
||||||
|
if ((ACC_ABSTRACT & access) != 0) {
|
||||||
|
s += "abstract ";
|
||||||
|
}
|
||||||
|
if ((ACC_STRICTFP & access) != 0) {
|
||||||
|
s += "strictfp ";
|
||||||
|
}
|
||||||
|
if ((ACC_SYNTHETIC & access) != 0) {
|
||||||
|
s += "synthetic ";
|
||||||
|
}
|
||||||
|
if ((ACC_BRIDGE & access) != 0) {
|
||||||
|
s += "bridge ";
|
||||||
|
}
|
||||||
|
if ((ACC_ANNOTATION & access) != 0) {
|
||||||
|
s += "annotation ";
|
||||||
|
}
|
||||||
|
if ((ACC_ENUM & access) != 0) {
|
||||||
|
s += "enum ";
|
||||||
|
}
|
||||||
|
if ((ACC_MANDATED & access) != 0) {
|
||||||
|
s += "mandated ";
|
||||||
|
}
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return { JavaType, Import }
|
||||||
|
*/
|
||||||
|
public static String[] reverseDescField(String desc) {
|
||||||
|
String type;
|
||||||
|
String importName = null;
|
||||||
|
int arrSize = 0;
|
||||||
|
while (desc.startsWith("[")) {
|
||||||
|
arrSize++;
|
||||||
|
desc = desc.substring(1);
|
||||||
|
}
|
||||||
|
switch (desc.substring(0, 1)) {
|
||||||
|
case "B":
|
||||||
|
type = "byte";
|
||||||
|
break;
|
||||||
|
case "Z":
|
||||||
|
type = "boolean";
|
||||||
|
break;
|
||||||
|
case "I":
|
||||||
|
type = "int";
|
||||||
|
break;
|
||||||
|
case "J":
|
||||||
|
type = "long";
|
||||||
|
break;
|
||||||
|
case "C":
|
||||||
|
type = "char";
|
||||||
|
break;
|
||||||
|
case "D":
|
||||||
|
type = "double";
|
||||||
|
break;
|
||||||
|
case "F":
|
||||||
|
type = "float";
|
||||||
|
break;
|
||||||
|
case "S":
|
||||||
|
type = "short";
|
||||||
|
break;
|
||||||
|
case "V":
|
||||||
|
type = "void";
|
||||||
|
break;
|
||||||
|
case "L": {
|
||||||
|
importName = desc.substring(1, desc.length() - 1).replace("/", ".");
|
||||||
|
if (!importName.contains(".")) {
|
||||||
|
type = importName;
|
||||||
|
importName = null;
|
||||||
|
} else {
|
||||||
|
type = importName.split("\\.")[importName.split("\\.").length - 1];
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
type = "ERROR";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
for (int i = 0; i < arrSize; i++) {
|
||||||
|
type += "[]";
|
||||||
|
}
|
||||||
|
return new String[] { type, importName };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return { ReturnValue, ParameterValues, Imports } getrennt durch ";"
|
||||||
|
*/
|
||||||
|
public static String[] reverseDescMethod(String desc) {
|
||||||
|
HashSet<String> imports = new HashSet<>();
|
||||||
|
String[] returnType = reverseDescField(desc.split("\\)")[1]);
|
||||||
|
if (returnType[1] != null) {
|
||||||
|
imports.add(returnType[1]);
|
||||||
|
}
|
||||||
|
ArrayList<String> parameters = new ArrayList<>();
|
||||||
|
String parameterType = desc.split("\\)")[0].substring(1);
|
||||||
|
|
||||||
|
while (!parameterType.isEmpty()) {
|
||||||
|
String type = "";
|
||||||
|
int arrSize = 0;
|
||||||
|
while (parameterType.startsWith("[")) {
|
||||||
|
arrSize++;
|
||||||
|
parameterType = parameterType.substring(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (parameterType.substring(0, 1)) {
|
||||||
|
case "B":
|
||||||
|
type = "byte";
|
||||||
|
parameterType = parameterType.substring(1);
|
||||||
|
break;
|
||||||
|
case "Z":
|
||||||
|
type = "boolean";
|
||||||
|
parameterType = parameterType.substring(1);
|
||||||
|
break;
|
||||||
|
case "I":
|
||||||
|
type = "int";
|
||||||
|
parameterType = parameterType.substring(1);
|
||||||
|
break;
|
||||||
|
case "J":
|
||||||
|
type = "long";
|
||||||
|
parameterType = parameterType.substring(1);
|
||||||
|
break;
|
||||||
|
case "C":
|
||||||
|
type = "char";
|
||||||
|
parameterType = parameterType.substring(1);
|
||||||
|
break;
|
||||||
|
case "D":
|
||||||
|
type = "double";
|
||||||
|
parameterType = parameterType.substring(1);
|
||||||
|
break;
|
||||||
|
case "F":
|
||||||
|
type = "float";
|
||||||
|
parameterType = parameterType.substring(1);
|
||||||
|
break;
|
||||||
|
case "S":
|
||||||
|
type = "short";
|
||||||
|
parameterType = parameterType.substring(1);
|
||||||
|
break;
|
||||||
|
case "V":
|
||||||
|
type = "void";
|
||||||
|
parameterType = parameterType.substring(1);
|
||||||
|
break;
|
||||||
|
case "L": {
|
||||||
|
String rofl = "";
|
||||||
|
while (!parameterType.startsWith(";")) {
|
||||||
|
rofl += parameterType.substring(0, 1);
|
||||||
|
parameterType = parameterType.substring(1);
|
||||||
|
}
|
||||||
|
rofl += parameterType.substring(0, 1);
|
||||||
|
parameterType = parameterType.substring(1);
|
||||||
|
String importName = rofl.substring(1, rofl.length() - 1).replace("/", ".");
|
||||||
|
if (!importName.contains(".")) {
|
||||||
|
type = importName;
|
||||||
|
importName = null;
|
||||||
|
} else {
|
||||||
|
type = importName.split("\\.")[importName.split("\\.").length - 1];
|
||||||
|
}
|
||||||
|
if (importName != null) {
|
||||||
|
imports.add(importName);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
type = "ERROR";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < arrSize; i++) {
|
||||||
|
type += "[]";
|
||||||
|
}
|
||||||
|
parameters.add(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
String importStr = "";
|
||||||
|
for (String s : imports) {
|
||||||
|
if (importStr.isEmpty()) {
|
||||||
|
importStr += s;
|
||||||
|
} else {
|
||||||
|
importStr += ";" + s;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
String parameterStr = "";
|
||||||
|
for (String s : parameters) {
|
||||||
|
if (parameterStr.isEmpty()) {
|
||||||
|
parameterStr += s;
|
||||||
|
} else {
|
||||||
|
parameterStr += ";" + s;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new String[] { returnType[0], parameterStr, importStr };
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int countMethodParams(String desc) {
|
||||||
|
int count = 0;
|
||||||
|
String parameterType = desc.split("\\)")[0].substring(1);
|
||||||
|
|
||||||
|
while (!parameterType.isEmpty()) {
|
||||||
|
while (parameterType.startsWith("[")) {
|
||||||
|
parameterType = parameterType.substring(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (parameterType.substring(0, 1)) {
|
||||||
|
case "B":
|
||||||
|
case "Z":
|
||||||
|
case "I":
|
||||||
|
case "J":
|
||||||
|
case "C":
|
||||||
|
case "D":
|
||||||
|
case "F":
|
||||||
|
case "S":
|
||||||
|
case "V":
|
||||||
|
parameterType = parameterType.substring(1);
|
||||||
|
count++;
|
||||||
|
break;
|
||||||
|
case "L": {
|
||||||
|
while (!parameterType.startsWith(";")) {
|
||||||
|
parameterType = parameterType.substring(1);
|
||||||
|
}
|
||||||
|
parameterType = parameterType.substring(1);
|
||||||
|
count++;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getDecompiledValue(Object o, String desc) {
|
||||||
|
switch (o.getClass().getName()) {
|
||||||
|
case "java.lang.String": {
|
||||||
|
return "\"" + StringEscapeUtils.escapeJava((String) o).replace("\n", "\\n").replace("\r", "\\r") + "\"";
|
||||||
|
}
|
||||||
|
case "java.lang.Integer": {
|
||||||
|
if (desc.equals("Z")) {
|
||||||
|
return ((int) o) == 0 ? "false" : "true";
|
||||||
|
}
|
||||||
|
return o.toString();
|
||||||
|
}
|
||||||
|
case "java.lang.Long": {
|
||||||
|
return o.toString() + "l";
|
||||||
|
}
|
||||||
|
case "java.lang.Float": {
|
||||||
|
return o.toString() + "f";
|
||||||
|
}
|
||||||
|
// case "java.util.ArrayList": {
|
||||||
|
// ArrayList list = (ArrayList) o;
|
||||||
|
// for (Object obj : list) {
|
||||||
|
// System.out.println(obj.getClass().getName());
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
default:
|
||||||
|
return o.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Object getCastedValue(String o, String className) {
|
||||||
|
switch (className) {
|
||||||
|
case "java/lang/String": {
|
||||||
|
return o;
|
||||||
|
}
|
||||||
|
case "java/lang/Integer": {
|
||||||
|
return Integer.parseInt(o);
|
||||||
|
}
|
||||||
|
case "java/lang/Long": {
|
||||||
|
return Long.parseLong(o);
|
||||||
|
}
|
||||||
|
case "java/lang/Float": {
|
||||||
|
return Float.parseFloat(o);
|
||||||
|
}
|
||||||
|
case "java/lang/Double": {
|
||||||
|
return Double.parseDouble(o);
|
||||||
|
}
|
||||||
|
case "java/lang/Boolean": {
|
||||||
|
return Boolean.parseBoolean(o);
|
||||||
|
}
|
||||||
|
case "java/lang/Short": {
|
||||||
|
return Short.parseShort(o);
|
||||||
|
}
|
||||||
|
case "java/lang/Byte": {
|
||||||
|
return Byte.parseByte(o);
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return o.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getSimpleDesc(String type) {
|
||||||
|
String desc = "";
|
||||||
|
|
||||||
|
for (int i = 0; i < countArraySize(type); i++) {
|
||||||
|
desc += "[";
|
||||||
|
}
|
||||||
|
|
||||||
|
type = type.replace("[]", "");
|
||||||
|
|
||||||
|
if (VARS.containsKey(type)) {
|
||||||
|
desc += VARS.get(type);
|
||||||
|
} else {
|
||||||
|
desc += "L" + type + ";";
|
||||||
|
}
|
||||||
|
|
||||||
|
return desc;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int countArraySize(String type) {
|
||||||
|
int size = 0;
|
||||||
|
|
||||||
|
for (byte b : type.getBytes()) {
|
||||||
|
if (b == ']') {
|
||||||
|
size++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final HashMap<String, String> VARS = new HashMap<>();
|
||||||
|
|
||||||
|
static {
|
||||||
|
VARS.put("byte", "B");
|
||||||
|
VARS.put("char", "C");
|
||||||
|
VARS.put("double", "D");
|
||||||
|
VARS.put("float", "F");
|
||||||
|
VARS.put("int", "I");
|
||||||
|
VARS.put("long", "J");
|
||||||
|
VARS.put("short", "S");
|
||||||
|
VARS.put("boolean", "Z");
|
||||||
|
VARS.put("void", "V");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int externalArrayTypeDimensionCount(String s) {
|
||||||
|
int i = 0;
|
||||||
|
int j = "[]".length();
|
||||||
|
int k = s.length() - j;
|
||||||
|
while (s.regionMatches(k, "[]", 0, j)) {
|
||||||
|
i++;
|
||||||
|
k -= j;
|
||||||
|
}
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String internalMethodDescriptor(String returnType, String methodArgs) {
|
||||||
|
returnType = returnType.replace(".", "/");
|
||||||
|
methodArgs = methodArgs.replace(".", "/");
|
||||||
|
StringBuffer buf = new StringBuffer();
|
||||||
|
buf.append('(');
|
||||||
|
ExternalTypeEnumeration type = new ExternalTypeEnumeration(methodArgs);
|
||||||
|
while (type.hasMoreTypes()) {
|
||||||
|
buf.append(getSimpleDesc(type.nextType()));
|
||||||
|
}
|
||||||
|
buf.append(')');
|
||||||
|
buf.append(getSimpleDesc(returnType));
|
||||||
|
|
||||||
|
return buf.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
package me.ByteEdit.main;
|
||||||
|
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.Reader;
|
||||||
|
|
||||||
|
public class CustomBufferedReader extends BufferedReader {
|
||||||
|
|
||||||
|
public int currentLine = 0;
|
||||||
|
|
||||||
|
public CustomBufferedReader(Reader in) {
|
||||||
|
super(in);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String readLine() throws IOException {
|
||||||
|
currentLine++;
|
||||||
|
return super.readLine();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,522 @@
|
|||||||
|
package me.ByteEdit.main;
|
||||||
|
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.objectweb.asm.Handle;
|
||||||
|
import org.objectweb.asm.Label;
|
||||||
|
import org.objectweb.asm.Type;
|
||||||
|
import org.objectweb.asm.tree.AbstractInsnNode;
|
||||||
|
import org.objectweb.asm.tree.AnnotationNode;
|
||||||
|
import org.objectweb.asm.tree.ClassNode;
|
||||||
|
import org.objectweb.asm.tree.FieldInsnNode;
|
||||||
|
import org.objectweb.asm.tree.FieldNode;
|
||||||
|
import org.objectweb.asm.tree.FrameNode;
|
||||||
|
import org.objectweb.asm.tree.IincInsnNode;
|
||||||
|
import org.objectweb.asm.tree.InsnNode;
|
||||||
|
import org.objectweb.asm.tree.IntInsnNode;
|
||||||
|
import org.objectweb.asm.tree.InvokeDynamicInsnNode;
|
||||||
|
import org.objectweb.asm.tree.JumpInsnNode;
|
||||||
|
import org.objectweb.asm.tree.LabelNode;
|
||||||
|
import org.objectweb.asm.tree.LdcInsnNode;
|
||||||
|
import org.objectweb.asm.tree.LineNumberNode;
|
||||||
|
import org.objectweb.asm.tree.LocalVariableNode;
|
||||||
|
import org.objectweb.asm.tree.LookupSwitchInsnNode;
|
||||||
|
import org.objectweb.asm.tree.MethodInsnNode;
|
||||||
|
import org.objectweb.asm.tree.MethodNode;
|
||||||
|
import org.objectweb.asm.tree.MultiANewArrayInsnNode;
|
||||||
|
import org.objectweb.asm.tree.TableSwitchInsnNode;
|
||||||
|
import org.objectweb.asm.tree.TypeInsnNode;
|
||||||
|
import org.objectweb.asm.tree.VarInsnNode;
|
||||||
|
|
||||||
|
public class Disassembler {
|
||||||
|
|
||||||
|
public static String disassemble(ClassNode classNode) {
|
||||||
|
if (classNode == null) {
|
||||||
|
return "ClassNode is null! This is not a valid java class file!";
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
HashMap<String, HashMap<Label, Integer>> classLabelMap;
|
||||||
|
if ((classLabelMap = Main.labels.get(classNode.name)) == null) {
|
||||||
|
classLabelMap = new HashMap<String, HashMap<Label, Integer>>();
|
||||||
|
Main.labels.put(classNode.name, classLabelMap);
|
||||||
|
} else {
|
||||||
|
classLabelMap.clear();
|
||||||
|
}
|
||||||
|
String s = "";
|
||||||
|
s += "// #Annotations\n";
|
||||||
|
if (classNode.visibleAnnotations != null && !classNode.visibleAnnotations.isEmpty()) {
|
||||||
|
for (AnnotationNode annotationNode : classNode.visibleAnnotations) {
|
||||||
|
s += "@" + annotationNode.desc;
|
||||||
|
if (annotationNode.values != null && !annotationNode.values.isEmpty()) {
|
||||||
|
s += " (";
|
||||||
|
boolean valBefore = true;
|
||||||
|
for (Object o : annotationNode.values) {
|
||||||
|
if (valBefore) {
|
||||||
|
s += o + " = [";
|
||||||
|
valBefore = false;
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
if (o instanceof String[]) {
|
||||||
|
String[] arr = (String[]) o;
|
||||||
|
boolean w8ing = false;
|
||||||
|
for (String rofl : arr) {
|
||||||
|
if (!w8ing) {
|
||||||
|
s += rofl + "/";
|
||||||
|
w8ing = true;
|
||||||
|
} else {
|
||||||
|
s += rofl + "]";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s += ", ";
|
||||||
|
} else if (o instanceof List) {
|
||||||
|
List list = (List) o;
|
||||||
|
s += "{ ";
|
||||||
|
for (Object obj : list) {
|
||||||
|
String[] arr = (String[]) obj;
|
||||||
|
boolean w8ing = false;
|
||||||
|
for (String rofl : arr) {
|
||||||
|
if (!w8ing) {
|
||||||
|
s += rofl + "/";
|
||||||
|
w8ing = true;
|
||||||
|
} else {
|
||||||
|
s += rofl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s += ", ";
|
||||||
|
}
|
||||||
|
s = s.substring(0, s.length() - 2);
|
||||||
|
s += " }]";
|
||||||
|
} else {
|
||||||
|
s += "(" + o.getClass().getName().replace(".", "/") + ") " + o + "], ";
|
||||||
|
}
|
||||||
|
valBefore = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (s.endsWith(", "))
|
||||||
|
s = s.substring(0, s.length() - 2);
|
||||||
|
s += ")";
|
||||||
|
}
|
||||||
|
s += "\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s += "// #Class v:" + classNode.version + "\n";
|
||||||
|
s += ClassUtil.getAccessFlagsClass(classNode.access) + classNode.name + " ";
|
||||||
|
s += "extends " + classNode.superName + " ";
|
||||||
|
if (classNode.interfaces != null && !classNode.interfaces.isEmpty()) {
|
||||||
|
String interfaceStr = "";
|
||||||
|
for (String interfc : classNode.interfaces) {
|
||||||
|
if (interfaceStr.isEmpty()) {
|
||||||
|
interfaceStr += interfc;
|
||||||
|
} else {
|
||||||
|
interfaceStr += ", " + interfc;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!interfaceStr.isEmpty())
|
||||||
|
s += "implements " + interfaceStr + " ";
|
||||||
|
}
|
||||||
|
s += "{\n// #SourceFile: " + (classNode.sourceFile == null ? "null" : classNode.sourceFile)
|
||||||
|
+ "\n\n// #Fields\n";
|
||||||
|
|
||||||
|
for (FieldNode fn : classNode.fields) {
|
||||||
|
if (fn.visibleAnnotations != null && !fn.visibleAnnotations.isEmpty()) {
|
||||||
|
for (AnnotationNode annotationNode : fn.visibleAnnotations) {
|
||||||
|
s += "\t@" + annotationNode.desc;
|
||||||
|
if (annotationNode.values != null && !annotationNode.values.isEmpty()) {
|
||||||
|
s += " (";
|
||||||
|
boolean valBefore = true;
|
||||||
|
for (Object o : annotationNode.values) {
|
||||||
|
if (valBefore) {
|
||||||
|
s += o + " = [";
|
||||||
|
valBefore = false;
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
if (o instanceof String[]) {
|
||||||
|
String[] arr = (String[]) o;
|
||||||
|
boolean w8ing = false;
|
||||||
|
for (String rofl : arr) {
|
||||||
|
if (!w8ing) {
|
||||||
|
s += rofl + "/";
|
||||||
|
w8ing = true;
|
||||||
|
} else {
|
||||||
|
s += rofl + "]";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s += ", ";
|
||||||
|
} else if (o instanceof List) {
|
||||||
|
List list = (List) o;
|
||||||
|
s += "{ ";
|
||||||
|
for (Object obj : list) {
|
||||||
|
String[] arr = (String[]) obj;
|
||||||
|
boolean w8ing = false;
|
||||||
|
for (String rofl : arr) {
|
||||||
|
if (!w8ing) {
|
||||||
|
s += rofl + "/";
|
||||||
|
w8ing = true;
|
||||||
|
} else {
|
||||||
|
s += rofl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s += ", ";
|
||||||
|
}
|
||||||
|
s = s.substring(0, s.length() - 2);
|
||||||
|
s += " }]";
|
||||||
|
} else {
|
||||||
|
s += "(" + o.getClass().getName().replace(".", "/") + ") " + o + "], ";
|
||||||
|
}
|
||||||
|
valBefore = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (s.endsWith(", "))
|
||||||
|
s = s.substring(0, s.length() - 2);
|
||||||
|
s += ")";
|
||||||
|
}
|
||||||
|
s += "\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s += "\t" + ClassUtil.getAccessFlagsFull(fn.access) + fn.desc + " " + fn.name;
|
||||||
|
if (fn.value != null) {
|
||||||
|
s += " = " + ClassUtil.getDecompiledValue(fn.value, fn.desc);
|
||||||
|
}
|
||||||
|
s += "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
s += "\n// #Methods\n";
|
||||||
|
|
||||||
|
for (MethodNode mn : classNode.methods) {
|
||||||
|
try {
|
||||||
|
HashMap<Label, Integer> methodLabelMap;
|
||||||
|
if ((methodLabelMap = classLabelMap.get(mn.name + "|" + mn.desc)) == null) {
|
||||||
|
methodLabelMap = new HashMap<Label, Integer>();
|
||||||
|
classLabelMap.put(mn.name + "|" + mn.desc, methodLabelMap);
|
||||||
|
}
|
||||||
|
s += "\t// #Max: l:" + mn.maxLocals + " s:" + mn.maxStack + "\n";
|
||||||
|
|
||||||
|
String[] dis = disassembleMethod(classNode.name, mn, methodLabelMap);
|
||||||
|
s += dis[1];
|
||||||
|
|
||||||
|
if (mn.visibleAnnotations != null && !mn.visibleAnnotations.isEmpty()) {
|
||||||
|
for (AnnotationNode annotationNode : mn.visibleAnnotations) {
|
||||||
|
s += "\t@" + annotationNode.desc;
|
||||||
|
if (annotationNode.values != null && !annotationNode.values.isEmpty()) {
|
||||||
|
s += " (";
|
||||||
|
boolean valBefore = true;
|
||||||
|
for (Object o : annotationNode.values) {
|
||||||
|
if (valBefore) {
|
||||||
|
s += o + " = [";
|
||||||
|
valBefore = false;
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
if (o instanceof String[]) {
|
||||||
|
String[] arr = (String[]) o;
|
||||||
|
boolean w8ing = false;
|
||||||
|
for (String rofl : arr) {
|
||||||
|
if (!w8ing) {
|
||||||
|
s += rofl + "/";
|
||||||
|
w8ing = true;
|
||||||
|
} else {
|
||||||
|
s += rofl + "]";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s += ", ";
|
||||||
|
} else if (o instanceof List) {
|
||||||
|
List list = (List) o;
|
||||||
|
s += "{ ";
|
||||||
|
for (Object obj : list) {
|
||||||
|
String[] arr = (String[]) obj;
|
||||||
|
boolean w8ing = false;
|
||||||
|
for (String rofl : arr) {
|
||||||
|
if (!w8ing) {
|
||||||
|
s += rofl + "/";
|
||||||
|
w8ing = true;
|
||||||
|
} else {
|
||||||
|
s += rofl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s += ", ";
|
||||||
|
}
|
||||||
|
s = s.substring(0, s.length() - 2);
|
||||||
|
s += " }]";
|
||||||
|
} else {
|
||||||
|
s += "(" + o.getClass().getName().replace(".", "/") + ") " + o + "], ";
|
||||||
|
}
|
||||||
|
valBefore = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (s.endsWith(", "))
|
||||||
|
s = s.substring(0, s.length() - 2);
|
||||||
|
s += ")";
|
||||||
|
}
|
||||||
|
s += "\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s += "\t" + ClassUtil.getAccessFlagsFull(mn.access) + mn.name + " " + mn.desc + " ";
|
||||||
|
if (mn.exceptions != null && !mn.exceptions.isEmpty()) {
|
||||||
|
String exceptionStr = "";
|
||||||
|
for (String exc : mn.exceptions) {
|
||||||
|
if (exceptionStr.isEmpty()) {
|
||||||
|
exceptionStr += exc;
|
||||||
|
} else {
|
||||||
|
exceptionStr += ", " + exc;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!exceptionStr.isEmpty())
|
||||||
|
s += "throws " + exceptionStr + " ";
|
||||||
|
}
|
||||||
|
s += "{\n";
|
||||||
|
s += dis[0];
|
||||||
|
s += "\t}\n\n";
|
||||||
|
} catch (Exception e) {
|
||||||
|
s += "\t\t// Method couldn't be disassembled:\n\t\t// "
|
||||||
|
+ e.getClass().getName() + ": " + e.getMessage() + "\n" + Arrays.toString(e.getStackTrace())
|
||||||
|
.replace(", ", "\n\t\t// \tat ").replace("[", "\t\t// \tat ").replace("]", "")
|
||||||
|
+ "\n\t}\n\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
s += "}\n";
|
||||||
|
return s;
|
||||||
|
} catch (Throwable e) {
|
||||||
|
return "Class couldn't be decompiled:\n" + e.getClass().getName() + ": " + e.getMessage() + "\n"
|
||||||
|
+ Arrays.toString(e.getStackTrace()).replace(", ", "\n\tat ").replace("[", "\tat ").replace("]", "")
|
||||||
|
+ "\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String[] disassembleMethod(String className, MethodNode mn, HashMap<Label, Integer> labels) {
|
||||||
|
String s = "";
|
||||||
|
String localVarTable = "\t// #LocalVars:\n";
|
||||||
|
for (AbstractInsnNode n : mn.instructions.toArray()) {
|
||||||
|
if (n instanceof LabelNode) {
|
||||||
|
labels.put(((LabelNode) n).getLabel(), labels.size() + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (mn.localVariables != null)
|
||||||
|
for (LocalVariableNode lvn : mn.localVariables) {
|
||||||
|
localVarTable += "\t// " + lvn.name + ": " + lvn.desc + " i:" + lvn.index + " s:"
|
||||||
|
+ labels.get(lvn.start.getLabel()) + " e:" + labels.get(lvn.end.getLabel()) + "\n";
|
||||||
|
}
|
||||||
|
for (AbstractInsnNode n : mn.instructions.toArray()) {
|
||||||
|
switch (n.getClass().getSimpleName()) {
|
||||||
|
case "FieldInsnNode": {
|
||||||
|
FieldInsnNode node = (FieldInsnNode) n;
|
||||||
|
s += "\t\t" + OpcodesReverse.reverseOpcode(node.getOpcode()) + " " + node.desc + " " + node.owner + "/"
|
||||||
|
+ node.name + "\n";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "LabelNode": {
|
||||||
|
LabelNode node = (LabelNode) n;
|
||||||
|
s += "\t\t// label " + labels.get(node.getLabel()) + "\n";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "FrameNode": {
|
||||||
|
FrameNode node = (FrameNode) n;
|
||||||
|
s += "\t\t// frame " + node.type + " l:";
|
||||||
|
ArrayList<String> arr = new ArrayList<>();
|
||||||
|
if (node.local != null) {
|
||||||
|
for (Object o : node.local) {
|
||||||
|
if (o == null) {
|
||||||
|
arr.add(null);
|
||||||
|
} else {
|
||||||
|
if (o instanceof String) {
|
||||||
|
arr.add((String) o);
|
||||||
|
} else {
|
||||||
|
arr.add("(" + o.getClass().getName().replace(".", "/") + ") " + o);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s += arr.toString();
|
||||||
|
} else {
|
||||||
|
s += "null";
|
||||||
|
}
|
||||||
|
arr.clear();
|
||||||
|
s += " s:";
|
||||||
|
if (node.stack != null) {
|
||||||
|
for (Object o : node.stack) {
|
||||||
|
if (o == null) {
|
||||||
|
arr.add(null);
|
||||||
|
} else {
|
||||||
|
if (o instanceof String) {
|
||||||
|
arr.add((String) o);
|
||||||
|
} else {
|
||||||
|
arr.add("(" + o.getClass().getName().replace(".", "/") + ") " + o);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s += arr.toString();
|
||||||
|
} else {
|
||||||
|
s += "null";
|
||||||
|
}
|
||||||
|
s += "\n";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "LineNumberNode": {
|
||||||
|
LineNumberNode node = (LineNumberNode) n;
|
||||||
|
s += "\t\t// line " + node.line + " " + labels.get(node.start.getLabel()) + "\n";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "InvokeDynamicInsnNode": {
|
||||||
|
InvokeDynamicInsnNode node = (InvokeDynamicInsnNode) n;
|
||||||
|
s += "\t\t" + OpcodesReverse.reverseOpcode(node.getOpcode()) + " [\n\t\t\tname: " + node.name
|
||||||
|
+ "\n\t\t\tdesc: " + node.desc + "\n\t\t\tHandle: [\n\t\t\t\tname: " + node.bsm.getName()
|
||||||
|
+ "\n\t\t\t\towner: " + node.bsm.getOwner() + "\n\t\t\t\tdesc: " + node.bsm.getDesc()
|
||||||
|
+ "\n\t\t\t\tisInterface: " + node.bsm.isInterface() + "\n\t\t\t\ttag: " + node.bsm.getTag()
|
||||||
|
+ "\n\t\t\t]\n\t\t\targs: [\n";
|
||||||
|
for (Object l : node.bsmArgs) {
|
||||||
|
if (l.getClass().getName().equals("org.objectweb.asm.Type")) {
|
||||||
|
Type type = (Type) l;
|
||||||
|
int off = 0;
|
||||||
|
int len = 0;
|
||||||
|
char[] buf = null;
|
||||||
|
try {
|
||||||
|
Field f_off = Type.class.getDeclaredField("off");
|
||||||
|
f_off.setAccessible(true);
|
||||||
|
off = f_off.getInt(type);
|
||||||
|
Field f_len = Type.class.getDeclaredField("len");
|
||||||
|
f_len.setAccessible(true);
|
||||||
|
len = f_len.getInt(type);
|
||||||
|
Field f_buf = Type.class.getDeclaredField("buf");
|
||||||
|
f_buf.setAccessible(true);
|
||||||
|
buf = (char[]) f_buf.get(type);
|
||||||
|
} catch (Exception e) {
|
||||||
|
}
|
||||||
|
s += "\t\t\t\tType: [\n\t\t\t\t\tsort: " + type.getSort() + "\n\t\t\t\t\toff: " + off
|
||||||
|
+ "\n\t\t\t\t\tlen: " + len + "\n\t\t\t\t\tbuf: "
|
||||||
|
+ ClassUtil.getDecompiledValue(new String(buf), "");
|
||||||
|
s += "\n\t\t\t\t]\n";
|
||||||
|
} else if (l.getClass().getName().equals("org.objectweb.asm.Handle")) {
|
||||||
|
Handle h = (Handle) l;
|
||||||
|
s += "\t\t\t\tHandle: [\n\t\t\t\t\tname: " + h.getName() + "\n\t\t\t\t\towner: " + h.getOwner()
|
||||||
|
+ "\n\t\t\t\t\tdesc: " + h.getDesc() + "\n\t\t\t\t\tisInterface: " + h.isInterface()
|
||||||
|
+ "\n\t\t\t\t\ttag: " + h.getTag();
|
||||||
|
s += "\n\t\t\t\t]\n";
|
||||||
|
} else {
|
||||||
|
s += "\t\t\t\t" + ClassUtil.getDecompiledValue(l, "") + "\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s += "\t\t\t]\n\t\t]\n";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "MethodInsnNode": {
|
||||||
|
MethodInsnNode node = (MethodInsnNode) n;
|
||||||
|
s += "\t\t" + OpcodesReverse.reverseOpcode(node.getOpcode()) + " " + node.desc + " " + node.owner + "/"
|
||||||
|
+ node.name + (node.itf ? " true" : "") + "\n";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "TypeInsnNode": {
|
||||||
|
TypeInsnNode node = (TypeInsnNode) n;
|
||||||
|
s += "\t\t" + OpcodesReverse.reverseOpcode(n.getOpcode()) + " " + node.desc + "\n";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "MultiANewArrayInsnNode": {
|
||||||
|
MultiANewArrayInsnNode node = (MultiANewArrayInsnNode) n;
|
||||||
|
s += "\t\t" + OpcodesReverse.reverseOpcode(node.getOpcode()) + " " + node.desc + " " + node.dims + "\n";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "LdcInsnNode": {
|
||||||
|
LdcInsnNode node = (LdcInsnNode) n;
|
||||||
|
switch (node.cst.getClass().getSimpleName()) {
|
||||||
|
case "String":
|
||||||
|
case "Double":
|
||||||
|
case "Integer":
|
||||||
|
case "Float":
|
||||||
|
case "Long": {
|
||||||
|
s += "\t\t" + OpcodesReverse.reverseOpcode(node.getOpcode()) + " "
|
||||||
|
+ ClassUtil.getDecompiledValue(node.cst, "") + "\n";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "Type": {
|
||||||
|
Type type = (Type) node.cst;
|
||||||
|
int off = 0;
|
||||||
|
int len = 0;
|
||||||
|
char[] buf = null;
|
||||||
|
try {
|
||||||
|
Field f_off = Type.class.getDeclaredField("off");
|
||||||
|
f_off.setAccessible(true);
|
||||||
|
off = f_off.getInt(type);
|
||||||
|
Field f_len = Type.class.getDeclaredField("len");
|
||||||
|
f_len.setAccessible(true);
|
||||||
|
len = f_len.getInt(type);
|
||||||
|
Field f_buf = Type.class.getDeclaredField("buf");
|
||||||
|
f_buf.setAccessible(true);
|
||||||
|
buf = (char[]) f_buf.get(type);
|
||||||
|
} catch (Exception e) {
|
||||||
|
}
|
||||||
|
s += "\t\t" + OpcodesReverse.reverseOpcode(n.getOpcode()) + " Type: [\n\t\t\tsort: "
|
||||||
|
+ type.getSort() + "\n\t\t\toff: " + off + "\n\t\t\tlen: " + len + "\n\t\t\tbuf: "
|
||||||
|
+ ClassUtil.getDecompiledValue(new String(buf), "");
|
||||||
|
s += "\n\t\t]\n";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
s += "\t\tUNRESOLVED TYPE! " + OpcodesReverse.reverseOpcode(node.getOpcode()) + " "
|
||||||
|
+ node.cst.getClass().getName().replace(".", "/") + " " + node.cst + "\n";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "VarInsnNode": {
|
||||||
|
VarInsnNode node = (VarInsnNode) n;
|
||||||
|
s += "\t\t" + OpcodesReverse.reverseOpcode(n.getOpcode()) + " " + node.var + "\n";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "InsnNode": {
|
||||||
|
InsnNode node = (InsnNode) n;
|
||||||
|
s += "\t\t" + OpcodesReverse.reverseOpcode(n.getOpcode()) + "\n";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "IntInsnNode": {
|
||||||
|
IntInsnNode node = (IntInsnNode) n;
|
||||||
|
s += "\t\t" + OpcodesReverse.reverseOpcode(n.getOpcode()) + " " + node.operand + "\n";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "JumpInsnNode": {
|
||||||
|
JumpInsnNode node = (JumpInsnNode) n;
|
||||||
|
s += "\t\t" + OpcodesReverse.reverseOpcode(n.getOpcode()) + " " + labels.get(node.label.getLabel())
|
||||||
|
+ "\n";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "TableSwitchInsnNode": {
|
||||||
|
TableSwitchInsnNode node = (TableSwitchInsnNode) n;
|
||||||
|
s += "\t\t" + OpcodesReverse.reverseOpcode(n.getOpcode()) + " [\n\t\t\tmin: " + node.min
|
||||||
|
+ "\n\t\t\tmax: " + node.max + "\n\t\t\tdefault: " + labels.get(node.dflt.getLabel())
|
||||||
|
+ "\n\t\t\tlabels: [\n";
|
||||||
|
for (LabelNode l : node.labels) {
|
||||||
|
s += "\t\t\t\t" + labels.get(l.getLabel()) + "\n";
|
||||||
|
}
|
||||||
|
s += "\t\t\t]\n\t\t]\n";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "LookupSwitchInsnNode": {
|
||||||
|
LookupSwitchInsnNode node = (LookupSwitchInsnNode) n;
|
||||||
|
s += "\t\t" + OpcodesReverse.reverseOpcode(n.getOpcode()) + " [\n\t\t\tdefault: "
|
||||||
|
+ labels.get(node.dflt.getLabel()) + "\n\t\t\tkeys: [\n";
|
||||||
|
for (Integer l : node.keys) {
|
||||||
|
s += "\t\t\t\t" + l + "\n";
|
||||||
|
}
|
||||||
|
s += "\t\t\t]" + "\n\t\t\tlabels: [\n";
|
||||||
|
for (LabelNode l : node.labels) {
|
||||||
|
s += "\t\t\t\t" + labels.get(l.getLabel()) + "\n";
|
||||||
|
}
|
||||||
|
s += "\t\t\t]\n\t\t]\n";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "IincInsnNode": {
|
||||||
|
IincInsnNode node = (IincInsnNode) n;
|
||||||
|
s += "\t\t" + OpcodesReverse.reverseOpcode(n.getOpcode()) + " " + node.var + " -> " + node.incr + "\n";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
s += "\t\tNOT HANDLED! " + OpcodesReverse.reverseOpcode(n.getOpcode()) + " "
|
||||||
|
+ n.getClass().getSimpleName() + "\n";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new String[] { s, localVarTable };
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
package me.ByteEdit.main;
|
||||||
|
|
||||||
|
public class ExternalTypeEnumeration {
|
||||||
|
private String descriptor;
|
||||||
|
private int index;
|
||||||
|
|
||||||
|
public ExternalTypeEnumeration(String paramString) {
|
||||||
|
setDescriptor(paramString);
|
||||||
|
}
|
||||||
|
|
||||||
|
ExternalTypeEnumeration() {
|
||||||
|
}
|
||||||
|
|
||||||
|
void setDescriptor(String paramString) {
|
||||||
|
this.descriptor = paramString;
|
||||||
|
|
||||||
|
reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void reset() {
|
||||||
|
this.index = (this.descriptor.indexOf('(') + 1);
|
||||||
|
if (this.index < 1) {
|
||||||
|
throw new IllegalArgumentException("Missing opening parenthesis in descriptor [" + this.descriptor + "]");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasMoreTypes() {
|
||||||
|
return this.index < this.descriptor.length() - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String nextType() {
|
||||||
|
int i = this.index;
|
||||||
|
|
||||||
|
this.index = this.descriptor.indexOf(',', i);
|
||||||
|
if (this.index < 0) {
|
||||||
|
this.index = this.descriptor.indexOf(')', i);
|
||||||
|
if (this.index < 0) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"Missing closing parenthesis in descriptor [" + this.descriptor + "]");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return this.descriptor.substring(i, this.index++).trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String methodName() {
|
||||||
|
return this.descriptor.substring(0, this.descriptor.indexOf('(')).trim();
|
||||||
|
}
|
||||||
|
}
|
||||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,484 @@
|
|||||||
|
package me.ByteEdit.main;
|
||||||
|
|
||||||
|
import java.awt.Color;
|
||||||
|
import java.awt.Component;
|
||||||
|
import java.awt.EventQueue;
|
||||||
|
import java.awt.Font;
|
||||||
|
import java.awt.GridLayout;
|
||||||
|
import java.awt.HeadlessException;
|
||||||
|
import java.awt.datatransfer.DataFlavor;
|
||||||
|
import java.awt.datatransfer.Transferable;
|
||||||
|
import java.awt.dnd.DropTarget;
|
||||||
|
import java.awt.dnd.DropTargetDragEvent;
|
||||||
|
import java.awt.dnd.DropTargetDropEvent;
|
||||||
|
import java.awt.dnd.DropTargetEvent;
|
||||||
|
import java.awt.dnd.DropTargetListener;
|
||||||
|
import java.awt.event.ActionEvent;
|
||||||
|
import java.awt.event.ActionListener;
|
||||||
|
import java.awt.event.KeyAdapter;
|
||||||
|
import java.awt.event.KeyEvent;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Enumeration;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map.Entry;
|
||||||
|
import java.util.jar.JarEntry;
|
||||||
|
import java.util.jar.JarFile;
|
||||||
|
import java.util.jar.JarOutputStream;
|
||||||
|
|
||||||
|
import javax.swing.JComponent;
|
||||||
|
import javax.swing.JDialog;
|
||||||
|
import javax.swing.JFileChooser;
|
||||||
|
import javax.swing.JFrame;
|
||||||
|
import javax.swing.JOptionPane;
|
||||||
|
import javax.swing.JPanel;
|
||||||
|
import javax.swing.JScrollPane;
|
||||||
|
import javax.swing.JSplitPane;
|
||||||
|
import javax.swing.JTree;
|
||||||
|
import javax.swing.KeyStroke;
|
||||||
|
import javax.swing.UIManager;
|
||||||
|
import javax.swing.border.EmptyBorder;
|
||||||
|
import javax.swing.event.TreeSelectionEvent;
|
||||||
|
import javax.swing.event.TreeSelectionListener;
|
||||||
|
import javax.swing.filechooser.FileNameExtensionFilter;
|
||||||
|
import javax.swing.tree.DefaultMutableTreeNode;
|
||||||
|
import javax.swing.tree.DefaultTreeCellRenderer;
|
||||||
|
import javax.swing.tree.DefaultTreeModel;
|
||||||
|
|
||||||
|
import org.apache.commons.io.IOUtils;
|
||||||
|
import org.fife.ui.autocomplete.AutoCompletion;
|
||||||
|
import org.fife.ui.autocomplete.BasicCompletion;
|
||||||
|
import org.fife.ui.autocomplete.CompletionProvider;
|
||||||
|
import org.fife.ui.autocomplete.DefaultCompletionProvider;
|
||||||
|
import org.fife.ui.autocomplete.ShorthandCompletion;
|
||||||
|
import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea;
|
||||||
|
import org.fife.ui.rsyntaxtextarea.SyntaxConstants;
|
||||||
|
import org.fife.ui.rsyntaxtextarea.Theme;
|
||||||
|
import org.fife.ui.rtextarea.RTextScrollPane;
|
||||||
|
import org.objectweb.asm.ClassReader;
|
||||||
|
import org.objectweb.asm.ClassWriter;
|
||||||
|
import org.objectweb.asm.Label;
|
||||||
|
import org.objectweb.asm.tree.AbstractInsnNode;
|
||||||
|
import org.objectweb.asm.tree.ClassNode;
|
||||||
|
import org.objectweb.asm.tree.MethodNode;
|
||||||
|
|
||||||
|
public class Main extends JFrame {
|
||||||
|
|
||||||
|
public static Main INSTANCE;
|
||||||
|
|
||||||
|
private JPanel contentPane;
|
||||||
|
private boolean isChangingFile;
|
||||||
|
private static File jarFile;
|
||||||
|
public static HashMap<String, byte[]> OTHER_FILES = new HashMap<>();
|
||||||
|
public static HashMap<String, ClassNode> classNodes = new HashMap<>();
|
||||||
|
public static String currentNodeName;
|
||||||
|
|
||||||
|
public static RSyntaxTextArea textArea;
|
||||||
|
public static SearchBox searchBox;
|
||||||
|
// Class, Method, LabelMap
|
||||||
|
public static HashMap<String, HashMap<String, HashMap<Label, Integer>>> labels = new HashMap<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Launch the application.
|
||||||
|
*/
|
||||||
|
public static void main(String[] args) {
|
||||||
|
EventQueue.invokeLater(new Runnable() {
|
||||||
|
public void run() {
|
||||||
|
try {
|
||||||
|
Main frame = new Main();
|
||||||
|
frame.setVisible(true);
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a simple provider that adds some Java-related completions.
|
||||||
|
*/
|
||||||
|
private CompletionProvider createCompletionProvider() {
|
||||||
|
DefaultCompletionProvider provider = new DefaultCompletionProvider();
|
||||||
|
|
||||||
|
for (int i = 0; i < 200; i++) {
|
||||||
|
String s = OpcodesReverse.reverseOpcode(i);
|
||||||
|
if (!s.startsWith("Unknown "))
|
||||||
|
provider.addCompletion(new BasicCompletion(provider, s));
|
||||||
|
}
|
||||||
|
|
||||||
|
provider.addCompletion(new BasicCompletion(provider, "public"));
|
||||||
|
provider.addCompletion(new BasicCompletion(provider, "private"));
|
||||||
|
provider.addCompletion(new BasicCompletion(provider, "protected"));
|
||||||
|
provider.addCompletion(new BasicCompletion(provider, "final"));
|
||||||
|
provider.addCompletion(new BasicCompletion(provider, "class"));
|
||||||
|
provider.addCompletion(new BasicCompletion(provider, "enum"));
|
||||||
|
provider.addCompletion(new BasicCompletion(provider, "static"));
|
||||||
|
provider.addCompletion(new BasicCompletion(provider, "strictfp"));
|
||||||
|
provider.addCompletion(new BasicCompletion(provider, "throws"));
|
||||||
|
provider.addCompletion(new BasicCompletion(provider, "synthetic"));
|
||||||
|
provider.addCompletion(new BasicCompletion(provider, "label"));
|
||||||
|
provider.addCompletion(new BasicCompletion(provider, "line"));
|
||||||
|
provider.addCompletion(new BasicCompletion(provider, "extends"));
|
||||||
|
provider.addCompletion(new BasicCompletion(provider, "implements"));
|
||||||
|
|
||||||
|
provider.addCompletion(new ShorthandCompletion(provider, "invvi", "invokevirtual desc owner/name"));
|
||||||
|
provider.addCompletion(new ShorthandCompletion(provider, "invif", "invokeinterface desc owner/name"));
|
||||||
|
provider.addCompletion(new ShorthandCompletion(provider, "invst", "invokestatic desc owner/name"));
|
||||||
|
provider.addCompletion(new ShorthandCompletion(provider, "invsp", "invokespecial desc owner/name"));
|
||||||
|
|
||||||
|
return provider;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create the frame.
|
||||||
|
*/
|
||||||
|
public Main() {
|
||||||
|
INSTANCE = this;
|
||||||
|
searchBox = new SearchBox();
|
||||||
|
setTitle("ByteEdit");
|
||||||
|
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
|
||||||
|
setBounds(100, 100, 813, 474);
|
||||||
|
try {
|
||||||
|
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
contentPane = new JPanel();
|
||||||
|
contentPane.setBackground(Color.GRAY);
|
||||||
|
contentPane.setBorder(new EmptyBorder(5, 5, 5, 5));
|
||||||
|
setContentPane(contentPane);
|
||||||
|
contentPane.setLayout(new GridLayout(0, 1, 0, 0));
|
||||||
|
|
||||||
|
JSplitPane splitPane = new JSplitPane();
|
||||||
|
splitPane.setBackground(Color.GRAY);
|
||||||
|
splitPane.setResizeWeight(0.2);
|
||||||
|
contentPane.add(splitPane);
|
||||||
|
|
||||||
|
JScrollPane scrollPane = new JScrollPane();
|
||||||
|
splitPane.setLeftComponent(scrollPane);
|
||||||
|
|
||||||
|
textArea = new RSyntaxTextArea();
|
||||||
|
|
||||||
|
CompletionProvider provider = createCompletionProvider();
|
||||||
|
|
||||||
|
AutoCompletion ac = new AutoCompletion(provider);
|
||||||
|
ac.install(textArea);
|
||||||
|
|
||||||
|
textArea.addKeyListener(new KeyAdapter() {
|
||||||
|
@Override
|
||||||
|
public void keyReleased(KeyEvent e) {
|
||||||
|
if (e.getKeyCode() == 116 && currentNodeName != null) {
|
||||||
|
try {
|
||||||
|
ClassNode classNode = classNodes.get(currentNodeName);
|
||||||
|
int prev = textArea.getCaretPosition();
|
||||||
|
String dis = Disassembler.disassemble(classNode);
|
||||||
|
String substr = currentNodeName.substring(0, currentNodeName.length() - 6);
|
||||||
|
for (String key : Main.classNodes.keySet()) {
|
||||||
|
if (key.contains("$")) {
|
||||||
|
String[] split = key.split("\\$");
|
||||||
|
if (split[0].equals(substr)) {
|
||||||
|
dis += "\n" + Disassembler.disassemble(classNodes.get(key));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
textArea.setText(dis);
|
||||||
|
textArea.setCaretPosition(prev);
|
||||||
|
} catch (Exception e2) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
KeyStroke ctrlF = KeyStroke.getKeyStroke(KeyEvent.VK_F, KeyEvent.CTRL_DOWN_MASK);
|
||||||
|
textArea.registerKeyboardAction(new ActionListener() {
|
||||||
|
@Override
|
||||||
|
public void actionPerformed(ActionEvent e) {
|
||||||
|
searchBox.setVisible(true);
|
||||||
|
searchBox.txtFind.requestFocusInWindow();
|
||||||
|
searchBox.txtFind.select(0, searchBox.txtFind.getText().length());
|
||||||
|
}
|
||||||
|
}, ctrlF, JComponent.WHEN_IN_FOCUSED_WINDOW);
|
||||||
|
KeyStroke ctrlS = KeyStroke.getKeyStroke(KeyEvent.VK_S, KeyEvent.CTRL_DOWN_MASK);
|
||||||
|
textArea.registerKeyboardAction(new ActionListener() {
|
||||||
|
@Override
|
||||||
|
public void actionPerformed(ActionEvent e) {
|
||||||
|
String txt = textArea.getText();
|
||||||
|
for (String s : txt.split("\\/\\/ #Annotations\n")) {
|
||||||
|
if (s.isEmpty())
|
||||||
|
continue;
|
||||||
|
ClassNode node = Assembler.assemble(s);
|
||||||
|
if (node == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
classNodes.replace(node.name + ".class", node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, ctrlS, JComponent.WHEN_FOCUSED);
|
||||||
|
textArea.setFont(new Font("Consolas", 0, 10));
|
||||||
|
textArea.setEditable(true);
|
||||||
|
textArea.setSyntaxEditingStyle(SyntaxConstants.SYNTAX_STYLE_JAVA_DISASSEMBLE);
|
||||||
|
textArea.setCodeFoldingEnabled(true);
|
||||||
|
try {
|
||||||
|
Theme theme = Theme.load(getClass().getClassLoader().getResourceAsStream("org/fife/eclipse.xml"));
|
||||||
|
theme.apply(textArea);
|
||||||
|
} catch (IOException e2) {
|
||||||
|
e2.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
JTree tree = new JTree(new DefaultTreeModel(null));
|
||||||
|
tree.addKeyListener(new KeyAdapter() {
|
||||||
|
@Override
|
||||||
|
public void keyReleased(KeyEvent e) {
|
||||||
|
if (e.getKeyCode() == 116 && jarFile != null) {
|
||||||
|
try {
|
||||||
|
labels.clear();
|
||||||
|
isChangingFile = true;
|
||||||
|
try {
|
||||||
|
tree.setModel(new ArchiveTreeModel(new JarFile(jarFile)));
|
||||||
|
} catch (IOException e1) {
|
||||||
|
e1.printStackTrace();
|
||||||
|
}
|
||||||
|
isChangingFile = false;
|
||||||
|
} catch (Throwable t) {
|
||||||
|
t.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
tree.registerKeyboardAction(new ActionListener() {
|
||||||
|
@Override
|
||||||
|
public void actionPerformed(ActionEvent e) {
|
||||||
|
save();
|
||||||
|
}
|
||||||
|
}, ctrlS, JComponent.WHEN_FOCUSED);
|
||||||
|
tree.setFont(new Font("Verdana", Font.PLAIN, 11));
|
||||||
|
tree.setBackground(Color.LIGHT_GRAY);
|
||||||
|
if (tree.getCellRenderer() instanceof DefaultTreeCellRenderer) {
|
||||||
|
final DefaultTreeCellRenderer renderer = (DefaultTreeCellRenderer) (tree.getCellRenderer());
|
||||||
|
renderer.setBackgroundNonSelectionColor(Color.LIGHT_GRAY);
|
||||||
|
renderer.setBackgroundSelectionColor(Color.GRAY);
|
||||||
|
renderer.setTextNonSelectionColor(Color.BLACK);
|
||||||
|
renderer.setTextSelectionColor(Color.BLACK);
|
||||||
|
}
|
||||||
|
scrollPane.setViewportView(tree);
|
||||||
|
tree.addTreeSelectionListener(new TreeSelectionListener() {
|
||||||
|
public void valueChanged(TreeSelectionEvent e) {
|
||||||
|
if (!isChangingFile) {
|
||||||
|
String s = "";
|
||||||
|
boolean b = false;
|
||||||
|
for (Object o : e.getPath().getPath()) {
|
||||||
|
DefaultMutableTreeNode path = (DefaultMutableTreeNode) o;
|
||||||
|
if (!b) {
|
||||||
|
b = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
s += (s.isEmpty() || s.replace("/", "").isEmpty() ? "" : "/")
|
||||||
|
+ (path.toString().isEmpty() ? "/" : path.toString());
|
||||||
|
}
|
||||||
|
if (s.endsWith(".class")) {
|
||||||
|
currentNodeName = s;
|
||||||
|
ClassNode classNode = classNodes.get(s);
|
||||||
|
String dis = Disassembler.disassemble(classNode);
|
||||||
|
String substr = s.substring(0, s.length() - 6);
|
||||||
|
for (String key : Main.classNodes.keySet()) {
|
||||||
|
if (key.contains("$")) {
|
||||||
|
String[] split = key.split("\\$");
|
||||||
|
if (split[0].equals(substr)) {
|
||||||
|
dis += "\n" + Disassembler.disassemble(classNodes.get(key));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
textArea.setText(dis);
|
||||||
|
textArea.setCaretPosition(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
RTextScrollPane scrollPane_1 = new RTextScrollPane();
|
||||||
|
scrollPane_1.setLineNumbersEnabled(true);
|
||||||
|
scrollPane_1.setFoldIndicatorEnabled(true);
|
||||||
|
textArea.setBackground(Color.LIGHT_GRAY);
|
||||||
|
scrollPane_1.setViewportView(textArea);
|
||||||
|
splitPane.setRightComponent(scrollPane_1);
|
||||||
|
new DropTarget(tree, new DropTargetListener() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void drop(DropTargetDropEvent dtde) {
|
||||||
|
try {
|
||||||
|
Transferable tr = dtde.getTransferable();
|
||||||
|
DataFlavor[] flavors = tr.getTransferDataFlavors();
|
||||||
|
for (int i = 0; i < flavors.length; i++) {
|
||||||
|
if (flavors[i].isFlavorJavaFileListType()) {
|
||||||
|
dtde.acceptDrop(dtde.getDropAction());
|
||||||
|
java.util.List<File> files = (java.util.List<File>) tr.getTransferData(flavors[i]);
|
||||||
|
|
||||||
|
final File file = files.get(0);
|
||||||
|
if (file.getName().endsWith(".jar")) {
|
||||||
|
labels.clear();
|
||||||
|
jarFile = file;
|
||||||
|
isChangingFile = true;
|
||||||
|
try {
|
||||||
|
tree.setModel(new ArchiveTreeModel(new JarFile(jarFile)));
|
||||||
|
} catch (IOException e1) {
|
||||||
|
e1.printStackTrace();
|
||||||
|
}
|
||||||
|
isChangingFile = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
dtde.dropComplete(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
} catch (Throwable t) {
|
||||||
|
t.printStackTrace();
|
||||||
|
}
|
||||||
|
dtde.rejectDrop();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void dragEnter(DropTargetDragEvent dtde) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void dragOver(DropTargetDragEvent dtde) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void dropActionChanged(DropTargetDragEvent dtde) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void dragExit(DropTargetEvent dte) {
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void save() {
|
||||||
|
final JFileChooser fileChooser = new JFileChooser() {
|
||||||
|
@Override
|
||||||
|
protected JDialog createDialog(final Component parent) throws HeadlessException {
|
||||||
|
final JDialog dialog = super.createDialog(parent);
|
||||||
|
dialog.setAlwaysOnTop(true);
|
||||||
|
return dialog;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
fileChooser.setDialogTitle("Save File");
|
||||||
|
fileChooser.setFileSelectionMode(0);
|
||||||
|
fileChooser.setAcceptAllFileFilterUsed(false);
|
||||||
|
fileChooser.addChoosableFileFilter(new FileNameExtensionFilter("Jar File", "jar"));
|
||||||
|
final int action = fileChooser.showOpenDialog(Main.this);
|
||||||
|
if (action == 0) {
|
||||||
|
final File file = fileChooser.getSelectedFile();
|
||||||
|
if (file.exists()) {
|
||||||
|
int dialogResult = JOptionPane.showConfirmDialog(null, "This file already exists! Overwrite it?",
|
||||||
|
"Warning", JOptionPane.YES_NO_OPTION);
|
||||||
|
if (dialogResult == JOptionPane.YES_OPTION) {
|
||||||
|
save(file, classNodes.values());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
save(file, classNodes.values());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void save(File jar, Collection<ClassNode> classes) {
|
||||||
|
try {
|
||||||
|
final JarOutputStream output = new JarOutputStream(new FileOutputStream(jar));
|
||||||
|
for (Entry<String, byte[]> entry : OTHER_FILES.entrySet()) {
|
||||||
|
JarEntry ent = new JarEntry(entry.getKey());
|
||||||
|
output.putNextEntry(ent);
|
||||||
|
output.write(entry.getValue());
|
||||||
|
output.closeEntry();
|
||||||
|
}
|
||||||
|
for (ClassNode element : classes) {
|
||||||
|
ClassWriter writer = new ClassWriter(0);
|
||||||
|
element.accept(writer);
|
||||||
|
output.putNextEntry(new JarEntry(element.name.replaceAll("\\.", "/") + ".class"));
|
||||||
|
output.write(writer.toByteArray());
|
||||||
|
output.closeEntry();
|
||||||
|
}
|
||||||
|
output.finish();
|
||||||
|
output.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ArchiveTreeModel extends DefaultTreeModel {
|
||||||
|
|
||||||
|
public ArchiveTreeModel(JarFile jar) {
|
||||||
|
super(new DefaultMutableTreeNode(jar.getName().split(File.separator.equals("\\") ? "\\\\"
|
||||||
|
: File.separator)[jar.getName().split(File.separator.equals("\\") ? "\\\\" : File.separator).length
|
||||||
|
- 1]));
|
||||||
|
|
||||||
|
try {
|
||||||
|
ArrayList<String> paths = new ArrayList<>();
|
||||||
|
classNodes.clear();
|
||||||
|
OTHER_FILES.clear();
|
||||||
|
Enumeration<JarEntry> enumeration = jar.entries();
|
||||||
|
while (enumeration.hasMoreElements()) {
|
||||||
|
JarEntry next = enumeration.nextElement();
|
||||||
|
byte[] data = IOUtils.toByteArray(jar.getInputStream(next));
|
||||||
|
if (next.getName().endsWith(".class")) {
|
||||||
|
if (next.getName().contains("/")
|
||||||
|
? (!next.getName().split("/")[next.getName().split("/").length - 1].contains("$"))
|
||||||
|
: (!next.getName().contains("$"))) {
|
||||||
|
paths.add(next.getName());
|
||||||
|
}
|
||||||
|
ClassReader reader;
|
||||||
|
try {
|
||||||
|
reader = new ClassReader(data);
|
||||||
|
} catch (Exception e) {
|
||||||
|
OTHER_FILES.put(next.getName(), data);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
ClassNode node = new ClassNode();
|
||||||
|
reader.accept(node, 0);
|
||||||
|
classNodes.put(next.getName(), node);
|
||||||
|
} else {
|
||||||
|
OTHER_FILES.put(next.getName(), data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
jar.close();
|
||||||
|
Collections.sort(paths);
|
||||||
|
for (String s : paths) {
|
||||||
|
String[] elements = s.split("/");
|
||||||
|
|
||||||
|
DefaultMutableTreeNode currentNode = (DefaultMutableTreeNode) getRoot();
|
||||||
|
|
||||||
|
for (int i = 0; i < elements.length; i++) {
|
||||||
|
String token = elements[i];
|
||||||
|
DefaultMutableTreeNode nextNode = findNode(currentNode, token);
|
||||||
|
if (nextNode == null) {
|
||||||
|
nextNode = new DefaultMutableTreeNode(token);
|
||||||
|
currentNode.add(nextNode);
|
||||||
|
}
|
||||||
|
currentNode = nextNode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
classNodes.clear();
|
||||||
|
OTHER_FILES.clear();
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private DefaultMutableTreeNode findNode(DefaultMutableTreeNode parent, String name) {
|
||||||
|
Enumeration<?> e = parent.children();
|
||||||
|
while (e.hasMoreElements()) {
|
||||||
|
DefaultMutableTreeNode element = (DefaultMutableTreeNode) e.nextElement();
|
||||||
|
if (element.getUserObject().equals(name)) {
|
||||||
|
return element;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,323 @@
|
|||||||
|
package me.ByteEdit.main;
|
||||||
|
|
||||||
|
public class OpcodesReverse {
|
||||||
|
|
||||||
|
public static String reverseOpcode(int opcode) {
|
||||||
|
switch (opcode) {
|
||||||
|
case 0:
|
||||||
|
return "nop";
|
||||||
|
case 1:
|
||||||
|
return "aconst_null";
|
||||||
|
case 2:
|
||||||
|
return "iconst_m1";
|
||||||
|
case 3:
|
||||||
|
return "iconst_0";
|
||||||
|
case 4:
|
||||||
|
return "iconst_1";
|
||||||
|
case 5:
|
||||||
|
return "iconst_2";
|
||||||
|
case 6:
|
||||||
|
return "iconst_3";
|
||||||
|
case 7:
|
||||||
|
return "iconst_4";
|
||||||
|
case 8:
|
||||||
|
return "iconst_5";
|
||||||
|
case 9:
|
||||||
|
return "lconst_0";
|
||||||
|
case 10:
|
||||||
|
return "lconst_1";
|
||||||
|
case 11:
|
||||||
|
return "fconst_0";
|
||||||
|
case 12:
|
||||||
|
return "fconst_1";
|
||||||
|
case 13:
|
||||||
|
return "fconst_2";
|
||||||
|
case 14:
|
||||||
|
return "dconst_0";
|
||||||
|
case 15:
|
||||||
|
return "dconst_1";
|
||||||
|
case 16:
|
||||||
|
return "bipush";
|
||||||
|
case 17:
|
||||||
|
return "sipush";
|
||||||
|
case 18:
|
||||||
|
return "ldc";
|
||||||
|
case 21:
|
||||||
|
return "iload";
|
||||||
|
case 22:
|
||||||
|
return "lload";
|
||||||
|
case 23:
|
||||||
|
return "fload";
|
||||||
|
case 24:
|
||||||
|
return "dload";
|
||||||
|
case 25:
|
||||||
|
return "aload";
|
||||||
|
case 46:
|
||||||
|
return "iaload";
|
||||||
|
case 47:
|
||||||
|
return "laload";
|
||||||
|
case 48:
|
||||||
|
return "faload";
|
||||||
|
case 49:
|
||||||
|
return "daload";
|
||||||
|
case 50:
|
||||||
|
return "aaload";
|
||||||
|
case 51:
|
||||||
|
return "baload";
|
||||||
|
case 52:
|
||||||
|
return "caload";
|
||||||
|
case 53:
|
||||||
|
return "saload";
|
||||||
|
case 54:
|
||||||
|
return "istore";
|
||||||
|
case 55:
|
||||||
|
return "lstore";
|
||||||
|
case 56:
|
||||||
|
return "fstore";
|
||||||
|
case 57:
|
||||||
|
return "dstore";
|
||||||
|
case 58:
|
||||||
|
return "astore";
|
||||||
|
case 79:
|
||||||
|
return "iastore";
|
||||||
|
case 80:
|
||||||
|
return "lastore";
|
||||||
|
case 81:
|
||||||
|
return "fastore";
|
||||||
|
case 82:
|
||||||
|
return "dastore";
|
||||||
|
case 83:
|
||||||
|
return "aastore";
|
||||||
|
case 84:
|
||||||
|
return "bastore";
|
||||||
|
case 85:
|
||||||
|
return "castore";
|
||||||
|
case 86:
|
||||||
|
return "sastore";
|
||||||
|
case 87:
|
||||||
|
return "pop";
|
||||||
|
case 88:
|
||||||
|
return "pop2";
|
||||||
|
case 89:
|
||||||
|
return "dup";
|
||||||
|
case 90:
|
||||||
|
return "dup_x1";
|
||||||
|
case 91:
|
||||||
|
return "dup_x2";
|
||||||
|
case 92:
|
||||||
|
return "dup2";
|
||||||
|
case 93:
|
||||||
|
return "dup2_x1";
|
||||||
|
case 94:
|
||||||
|
return "dup2_x2";
|
||||||
|
case 95:
|
||||||
|
return "swap";
|
||||||
|
case 96:
|
||||||
|
return "iadd";
|
||||||
|
case 97:
|
||||||
|
return "ladd";
|
||||||
|
case 98:
|
||||||
|
return "fadd";
|
||||||
|
case 99:
|
||||||
|
return "dadd";
|
||||||
|
case 100:
|
||||||
|
return "isub";
|
||||||
|
case 101:
|
||||||
|
return "lsub";
|
||||||
|
case 102:
|
||||||
|
return "fsub";
|
||||||
|
case 103:
|
||||||
|
return "dsub";
|
||||||
|
case 104:
|
||||||
|
return "imul";
|
||||||
|
case 105:
|
||||||
|
return "lmul";
|
||||||
|
case 106:
|
||||||
|
return "fmul";
|
||||||
|
case 107:
|
||||||
|
return "dmul";
|
||||||
|
case 108:
|
||||||
|
return "idiv";
|
||||||
|
case 109:
|
||||||
|
return "ldiv";
|
||||||
|
case 110:
|
||||||
|
return "fdiv";
|
||||||
|
case 111:
|
||||||
|
return "ddiv";
|
||||||
|
case 112:
|
||||||
|
return "irem";
|
||||||
|
case 113:
|
||||||
|
return "lrem";
|
||||||
|
case 114:
|
||||||
|
return "frem";
|
||||||
|
case 115:
|
||||||
|
return "drem";
|
||||||
|
case 116:
|
||||||
|
return "ineg";
|
||||||
|
case 117:
|
||||||
|
return "lneg";
|
||||||
|
case 118:
|
||||||
|
return "fneg";
|
||||||
|
case 119:
|
||||||
|
return "dneg";
|
||||||
|
case 120:
|
||||||
|
return "ishl";
|
||||||
|
case 121:
|
||||||
|
return "lshl";
|
||||||
|
case 122:
|
||||||
|
return "ishr";
|
||||||
|
case 123:
|
||||||
|
return "lshr";
|
||||||
|
case 124:
|
||||||
|
return "iushr";
|
||||||
|
case 125:
|
||||||
|
return "lushr";
|
||||||
|
case 126:
|
||||||
|
return "iand";
|
||||||
|
case 127:
|
||||||
|
return "land";
|
||||||
|
case 128:
|
||||||
|
return "ior";
|
||||||
|
case 129:
|
||||||
|
return "lor";
|
||||||
|
case 130:
|
||||||
|
return "ixor";
|
||||||
|
case 131:
|
||||||
|
return "lxor";
|
||||||
|
case 132:
|
||||||
|
return "iinc";
|
||||||
|
case 133:
|
||||||
|
return "i2l";
|
||||||
|
case 134:
|
||||||
|
return "i2f";
|
||||||
|
case 135:
|
||||||
|
return "i2d";
|
||||||
|
case 136:
|
||||||
|
return "l2i";
|
||||||
|
case 137:
|
||||||
|
return "l2f";
|
||||||
|
case 138:
|
||||||
|
return "l2d";
|
||||||
|
case 139:
|
||||||
|
return "f2i";
|
||||||
|
case 140:
|
||||||
|
return "f2l";
|
||||||
|
case 141:
|
||||||
|
return "f2d";
|
||||||
|
case 142:
|
||||||
|
return "d2i";
|
||||||
|
case 143:
|
||||||
|
return "d2l";
|
||||||
|
case 144:
|
||||||
|
return "d2f";
|
||||||
|
case 145:
|
||||||
|
return "i2b";
|
||||||
|
case 146:
|
||||||
|
return "i2c";
|
||||||
|
case 147:
|
||||||
|
return "i2s";
|
||||||
|
case 148:
|
||||||
|
return "lcmp";
|
||||||
|
case 149:
|
||||||
|
return "fcmpl";
|
||||||
|
case 150:
|
||||||
|
return "fcmpg";
|
||||||
|
case 151:
|
||||||
|
return "dcmpl";
|
||||||
|
case 152:
|
||||||
|
return "dcmpg";
|
||||||
|
case 153:
|
||||||
|
return "ifeq";
|
||||||
|
case 154:
|
||||||
|
return "ifne";
|
||||||
|
case 155:
|
||||||
|
return "iflt";
|
||||||
|
case 156:
|
||||||
|
return "ifge";
|
||||||
|
case 157:
|
||||||
|
return "ifgt";
|
||||||
|
case 158:
|
||||||
|
return "ifle";
|
||||||
|
case 159:
|
||||||
|
return "if_icmpeq";
|
||||||
|
case 160:
|
||||||
|
return "if_icmpne";
|
||||||
|
case 161:
|
||||||
|
return "if_icmplt";
|
||||||
|
case 162:
|
||||||
|
return "if_icmpge";
|
||||||
|
case 163:
|
||||||
|
return "if_icmpgt";
|
||||||
|
case 164:
|
||||||
|
return "if_icmple";
|
||||||
|
case 165:
|
||||||
|
return "if_acmpeq";
|
||||||
|
case 166:
|
||||||
|
return "if_acmpne";
|
||||||
|
case 167:
|
||||||
|
return "goto";
|
||||||
|
case 170:
|
||||||
|
return "tableswitch";
|
||||||
|
case 171:
|
||||||
|
return "lookupswitch";
|
||||||
|
case 172:
|
||||||
|
return "ireturn";
|
||||||
|
case 173:
|
||||||
|
return "lreturn";
|
||||||
|
case 174:
|
||||||
|
return "freturn";
|
||||||
|
case 175:
|
||||||
|
return "dreturn";
|
||||||
|
case 176:
|
||||||
|
return "areturn";
|
||||||
|
case 177:
|
||||||
|
return "return";
|
||||||
|
case 178:
|
||||||
|
return "getstatic";
|
||||||
|
case 179:
|
||||||
|
return "putstatic";
|
||||||
|
case 180:
|
||||||
|
return "getfield";
|
||||||
|
case 181:
|
||||||
|
return "putfield";
|
||||||
|
case 182:
|
||||||
|
return "invokevirtual";
|
||||||
|
case 183:
|
||||||
|
return "invokespecial";
|
||||||
|
case 184:
|
||||||
|
return "invokestatic";
|
||||||
|
case 185:
|
||||||
|
return "invokeinterface";
|
||||||
|
case 186:
|
||||||
|
return "invokedynamic";
|
||||||
|
case 187:
|
||||||
|
return "new";
|
||||||
|
case 188:
|
||||||
|
return "newarray";
|
||||||
|
case 189:
|
||||||
|
return "anewarray";
|
||||||
|
case 190:
|
||||||
|
return "arraylength";
|
||||||
|
case 191:
|
||||||
|
return "athrow";
|
||||||
|
case 192:
|
||||||
|
return "checkcast";
|
||||||
|
case 193:
|
||||||
|
return "instanceof";
|
||||||
|
case 194:
|
||||||
|
return "monitorenter";
|
||||||
|
case 195:
|
||||||
|
return "monitorexit";
|
||||||
|
case 197:
|
||||||
|
return "multianewarray";
|
||||||
|
case 198:
|
||||||
|
return "ifnull";
|
||||||
|
case 199:
|
||||||
|
return "ifnonnul";
|
||||||
|
|
||||||
|
default:
|
||||||
|
return "Unknown Opcode: " + opcode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,152 @@
|
|||||||
|
package me.ByteEdit.main;
|
||||||
|
|
||||||
|
import java.awt.event.ActionEvent;
|
||||||
|
import java.awt.event.ActionListener;
|
||||||
|
import java.awt.event.KeyEvent;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
import javax.swing.JButton;
|
||||||
|
import javax.swing.JCheckBox;
|
||||||
|
import javax.swing.JComponent;
|
||||||
|
import javax.swing.JFrame;
|
||||||
|
import javax.swing.JLabel;
|
||||||
|
import javax.swing.JPanel;
|
||||||
|
import javax.swing.JTextField;
|
||||||
|
import javax.swing.KeyStroke;
|
||||||
|
import javax.swing.UIManager;
|
||||||
|
import javax.swing.border.EmptyBorder;
|
||||||
|
|
||||||
|
public class SearchBox extends JFrame {
|
||||||
|
|
||||||
|
public JPanel contentPane;
|
||||||
|
public JTextField txtFind;
|
||||||
|
public JTextField txtReplace;
|
||||||
|
public JCheckBox chckbxCaseSensitive;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create the frame.
|
||||||
|
*/
|
||||||
|
public SearchBox() {
|
||||||
|
try {
|
||||||
|
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
setResizable(false);
|
||||||
|
setTitle("Find/Replace");
|
||||||
|
setDefaultCloseOperation(JFrame.HIDE_ON_CLOSE);
|
||||||
|
setBounds(100, 100, 256, 263);
|
||||||
|
contentPane = new JPanel();
|
||||||
|
contentPane.setBorder(new EmptyBorder(5, 5, 5, 5));
|
||||||
|
setContentPane(contentPane);
|
||||||
|
contentPane.setLayout(null);
|
||||||
|
|
||||||
|
JLabel lblFind = new JLabel("Find:");
|
||||||
|
lblFind.setBounds(10, 25, 71, 14);
|
||||||
|
contentPane.add(lblFind);
|
||||||
|
|
||||||
|
txtFind = new JTextField();
|
||||||
|
KeyStroke enter = KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0);
|
||||||
|
txtFind.registerKeyboardAction(new ActionListener() {
|
||||||
|
@Override
|
||||||
|
public void actionPerformed(ActionEvent e) {
|
||||||
|
find();
|
||||||
|
}
|
||||||
|
}, enter, JComponent.WHEN_IN_FOCUSED_WINDOW);
|
||||||
|
txtFind.setBounds(91, 22, 149, 20);
|
||||||
|
contentPane.add(txtFind);
|
||||||
|
txtFind.setColumns(10);
|
||||||
|
|
||||||
|
JLabel lblReplaceWith = new JLabel("Replace with:");
|
||||||
|
lblReplaceWith.setBounds(10, 53, 71, 14);
|
||||||
|
contentPane.add(lblReplaceWith);
|
||||||
|
|
||||||
|
txtReplace = new JTextField();
|
||||||
|
txtReplace.setColumns(10);
|
||||||
|
txtReplace.setBounds(91, 50, 149, 20);
|
||||||
|
contentPane.add(txtReplace);
|
||||||
|
|
||||||
|
chckbxCaseSensitive = new JCheckBox("Case sensitive");
|
||||||
|
chckbxCaseSensitive.setBounds(10, 103, 230, 23);
|
||||||
|
contentPane.add(chckbxCaseSensitive);
|
||||||
|
|
||||||
|
JButton btnFind = new JButton("Find");
|
||||||
|
btnFind.addActionListener(new ActionListener() {
|
||||||
|
public void actionPerformed(ActionEvent e) {
|
||||||
|
find();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
btnFind.setBounds(10, 150, 110, 23);
|
||||||
|
contentPane.add(btnFind);
|
||||||
|
|
||||||
|
JButton btnReplacefind = new JButton("Replace/Find");
|
||||||
|
btnReplacefind.addActionListener(new ActionListener() {
|
||||||
|
public void actionPerformed(ActionEvent e) {
|
||||||
|
replaceFind();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
btnReplacefind.setBounds(130, 150, 110, 23);
|
||||||
|
contentPane.add(btnReplacefind);
|
||||||
|
|
||||||
|
JButton btnReplace = new JButton("Replace");
|
||||||
|
btnReplace.addActionListener(new ActionListener() {
|
||||||
|
public void actionPerformed(ActionEvent e) {
|
||||||
|
replace();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
btnReplace.setBounds(10, 184, 110, 23);
|
||||||
|
contentPane.add(btnReplace);
|
||||||
|
|
||||||
|
JButton btnReplaceAll = new JButton("Replace All");
|
||||||
|
btnReplaceAll.addActionListener(new ActionListener() {
|
||||||
|
public void actionPerformed(ActionEvent e) {
|
||||||
|
replaceAll();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
btnReplaceAll.setBounds(130, 184, 110, 23);
|
||||||
|
contentPane.add(btnReplaceAll);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void find() {
|
||||||
|
int startPos = Main.textArea.getCaretPosition();
|
||||||
|
String toFind = txtFind.getText();
|
||||||
|
String txt = chckbxCaseSensitive.isSelected() ? Main.textArea.getText().substring(startPos)
|
||||||
|
: Main.textArea.getText().substring(startPos).toLowerCase();
|
||||||
|
int index = txt.indexOf(chckbxCaseSensitive.isSelected() ? toFind : toFind.toLowerCase());
|
||||||
|
if (index == -1) {
|
||||||
|
txt = chckbxCaseSensitive.isSelected() ? Main.textArea.getText() : Main.textArea.getText().toLowerCase();
|
||||||
|
index = txt.indexOf(chckbxCaseSensitive.isSelected() ? toFind : toFind.toLowerCase());
|
||||||
|
startPos = 0;
|
||||||
|
if (index == -1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Main.textArea.select(startPos + index, startPos + index + toFind.length());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void replaceFind() {
|
||||||
|
replace();
|
||||||
|
find();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void replace() {
|
||||||
|
String txt = Main.textArea.getText();
|
||||||
|
int startPos = Main.textArea.getSelectionStart();
|
||||||
|
Main.textArea.replaceSelection(txtReplace.getText());
|
||||||
|
Main.textArea.select(startPos, startPos + txtReplace.getText().length());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void replaceAll() {
|
||||||
|
String toFind = txtFind.getText();
|
||||||
|
String toReplace = txtReplace.getText();
|
||||||
|
String txt = Main.textArea.getText();
|
||||||
|
int prev = Main.textArea.getCaretPosition();
|
||||||
|
if (chckbxCaseSensitive.isSelected()) {
|
||||||
|
Main.textArea.replaceRange(txt.replace(toFind, toReplace), 0, txt.length());
|
||||||
|
} else {
|
||||||
|
Main.textArea.replaceRange(txt.replaceAll("(?i)" + Pattern.quote(toFind), toReplace), 0, txt.length());
|
||||||
|
}
|
||||||
|
Main.textArea.setCaretPosition(prev);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
|
<!DOCTYPE RSyntaxTheme SYSTEM "theme.dtd">
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Theme that mimics Eclipse's defaults.
|
||||||
|
See theme.dtd and org.fife.ui.rsyntaxtextarea.Theme for more information.
|
||||||
|
-->
|
||||||
|
<RSyntaxTheme version="1.0">
|
||||||
|
|
||||||
|
<!-- Omitting baseFont will use a system-appropriate monospaced. -->
|
||||||
|
<!--<baseFont family="..." size="13"/>-->
|
||||||
|
<baseFont size="12"/>
|
||||||
|
|
||||||
|
<!-- General editor colors. -->
|
||||||
|
<background color="ffffff"/>
|
||||||
|
<caret color="000000"/>
|
||||||
|
<selection bg="default" fg="default"/>
|
||||||
|
<currentLineHighlight color="dddddd" fade="false"/>
|
||||||
|
<marginLine fg="b0b4b9"/>
|
||||||
|
<markAllHighlight color="6b8189"/> <!-- TODO: Fix me -->
|
||||||
|
<markOccurrencesHighlight color="d4d4d4" border="false"/>
|
||||||
|
<matchedBracket fg="c0c0c0" highlightBoth="false" animate="false"/>
|
||||||
|
<hyperlinks fg="0000ff"/>
|
||||||
|
<secondaryLanguages>
|
||||||
|
<language index="1" bg="fff0cc"/>
|
||||||
|
<language index="2" bg="dafeda"/>
|
||||||
|
<language index="3" bg="ffe0f0"/>
|
||||||
|
</secondaryLanguages>
|
||||||
|
|
||||||
|
<!-- Gutter styling. -->
|
||||||
|
<gutterBorder color="dddddd"/>
|
||||||
|
<lineNumbers fg="787878"/>
|
||||||
|
<foldIndicator fg="808080" iconBg="ffffff"/>
|
||||||
|
<iconRowHeader activeLineRange="3399ff"/>
|
||||||
|
|
||||||
|
<!-- Syntax tokens. -->
|
||||||
|
<tokenStyles>
|
||||||
|
<style token="IDENTIFIER" fg="000000"/>
|
||||||
|
<style token="RESERVED_WORD" fg="7f0055" bold="true"/>
|
||||||
|
<style token="RESERVED_WORD_2" fg="7f0055" bold="true"/>
|
||||||
|
<style token="ANNOTATION" fg="808080"/>
|
||||||
|
<style token="COMMENT_DOCUMENTATION" fg="3f5fbf"/>
|
||||||
|
<style token="COMMENT_EOL" fg="3f7f5f"/>
|
||||||
|
<style token="COMMENT_MULTILINE" fg="3f7f5f"/>
|
||||||
|
<style token="COMMENT_KEYWORD" fg="7F9FBF" bold="true"/>
|
||||||
|
<style token="COMMENT_MARKUP" fg="7f7f9f"/>
|
||||||
|
<style token="DATA_TYPE" fg="7f0055" bold="true"/>
|
||||||
|
<style token="FUNCTION" fg="000000"/>
|
||||||
|
<style token="LITERAL_BOOLEAN" fg="7f0055" bold="true"/>
|
||||||
|
<style token="LITERAL_NUMBER_DECIMAL_INT" fg="000000"/>
|
||||||
|
<style token="LITERAL_NUMBER_FLOAT" fg="000000"/>
|
||||||
|
<style token="LITERAL_NUMBER_HEXADECIMAL" fg="000000"/>
|
||||||
|
<style token="LITERAL_STRING_DOUBLE_QUOTE" fg="2900ff"/>
|
||||||
|
<style token="LITERAL_CHAR" fg="2900ff"/>
|
||||||
|
<style token="LITERAL_BACKQUOTE" fg="2900ff"/>
|
||||||
|
<style token="MARKUP_TAG_DELIMITER" fg="008080"/>
|
||||||
|
<style token="MARKUP_TAG_NAME" fg="3f7f7f"/>
|
||||||
|
<style token="MARKUP_TAG_ATTRIBUTE" fg="7f007f"/>
|
||||||
|
<style token="MARKUP_TAG_ATTRIBUTE_VALUE" fg="2a00ff" italic="true"/>
|
||||||
|
<style token="MARKUP_COMMENT" fg="3f5fbf"/>
|
||||||
|
<style token="MARKUP_DTD" fg="008080"/>
|
||||||
|
<style token="MARKUP_PROCESSING_INSTRUCTION" fg="808080"/>
|
||||||
|
<style token="MARKUP_CDATA" fg="000000"/>
|
||||||
|
<style token="MARKUP_CDATA_DELIMITER" fg="008080"/>
|
||||||
|
<style token="MARKUP_ENTITY_REFERENCE" fg="2a00ff"/>
|
||||||
|
<style token="OPERATOR" fg="000000"/>
|
||||||
|
<style token="PREPROCESSOR" fg="808080"/>
|
||||||
|
<style token="REGEX" fg="008040"/>
|
||||||
|
<style token="SEPARATOR" fg="000000"/>
|
||||||
|
<style token="VARIABLE" fg="ff9900" bold="true"/>
|
||||||
|
<style token="WHITESPACE" fg="000000"/>
|
||||||
|
|
||||||
|
<style token="ERROR_IDENTIFIER" fg="000000" bg="ffcccc"/>
|
||||||
|
<style token="ERROR_NUMBER_FORMAT" fg="000000" bg="ffcccc"/>
|
||||||
|
<style token="ERROR_STRING_DOUBLE" fg="000000" bg="ffcccc"/>
|
||||||
|
<style token="ERROR_CHAR" fg="000000" bg="ffcccc"/>
|
||||||
|
</tokenStyles>
|
||||||
|
|
||||||
|
</RSyntaxTheme>
|
||||||
@@ -0,0 +1,221 @@
|
|||||||
|
/*
|
||||||
|
* 02/24/2004
|
||||||
|
*
|
||||||
|
* DocumentReader.java - A reader for javax.swing.text.Document
|
||||||
|
* objects.
|
||||||
|
*
|
||||||
|
* This library is distributed under a modified BSD license. See the included
|
||||||
|
* RSyntaxTextArea.License.txt file for details.
|
||||||
|
*/
|
||||||
|
package org.fife.io;
|
||||||
|
|
||||||
|
import java.io.Reader;
|
||||||
|
|
||||||
|
import javax.swing.text.BadLocationException;
|
||||||
|
import javax.swing.text.Document;
|
||||||
|
import javax.swing.text.Segment;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A <code>Reader</code> for <code>javax.swing.text.Document</code> objects.
|
||||||
|
*
|
||||||
|
* @author Robert Futrell
|
||||||
|
* @version 1.0
|
||||||
|
*/
|
||||||
|
public class DocumentReader extends Reader {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The stream's position in the document.
|
||||||
|
*/
|
||||||
|
private long position;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A remembered position in the document.
|
||||||
|
*/
|
||||||
|
private long mark;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The document we're working on.
|
||||||
|
*/
|
||||||
|
private Document document;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used for fast character access.
|
||||||
|
*/
|
||||||
|
private Segment segment;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
*
|
||||||
|
* @param document The document we're 'reading'.
|
||||||
|
*/
|
||||||
|
public DocumentReader(Document document) {
|
||||||
|
position = 0;
|
||||||
|
mark = -1;
|
||||||
|
this.document = document;
|
||||||
|
this.segment = new Segment();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This currently does nothing...
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Marks the present position in the stream. Subsequent calls to
|
||||||
|
* <code>reset()</code> will reposition the stream to this point.
|
||||||
|
*
|
||||||
|
* @param readAheadLimit Ignored.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void mark(int readAheadLimit) {
|
||||||
|
mark = position;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tells whether this reader supports the <code>mark</code> operation.
|
||||||
|
* This always returns <code>true</code> for <code>DocumentReader</code>.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean markSupported() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads the single character at the current position in the document.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public int read() {
|
||||||
|
if(position>=document.getLength()) {
|
||||||
|
return -1; // Read past end of document.
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
document.getText((int)position,1, segment);
|
||||||
|
position++;
|
||||||
|
return segment.array[segment.offset];
|
||||||
|
} catch (BadLocationException ble) {
|
||||||
|
/* Should never happen?? */
|
||||||
|
ble.printStackTrace();
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read <code>array.length</code> characters from the beginning
|
||||||
|
* of the document into <code>array</code>.
|
||||||
|
*
|
||||||
|
* @param array The array to read characters into.
|
||||||
|
* @return The number of characters read.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public int read(char[] array) {
|
||||||
|
return read(array, 0, array.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads characters into a portion of an array.
|
||||||
|
*
|
||||||
|
* @param cbuf The destination buffer.
|
||||||
|
* @param off Offset at which to start storing characters.
|
||||||
|
* @param len Maximum number of characters to read.
|
||||||
|
* @return The number of characters read, or <code>-1</code> if the
|
||||||
|
* end of the stream (document) has been reached.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public int read(char[] cbuf, int off, int len) {
|
||||||
|
int k;
|
||||||
|
if(position>=document.getLength()) {
|
||||||
|
return -1; // Read past end of document.
|
||||||
|
}
|
||||||
|
k = len;
|
||||||
|
if((position+k)>=document.getLength()) {
|
||||||
|
k = document.getLength() - (int)position;
|
||||||
|
}
|
||||||
|
if(off + k >= cbuf.length) {
|
||||||
|
k = cbuf.length - off;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
document.getText((int)position, k, segment);
|
||||||
|
position += k;
|
||||||
|
System.arraycopy(segment.array,segment.offset,
|
||||||
|
cbuf,off,
|
||||||
|
k);
|
||||||
|
return k;
|
||||||
|
} catch (BadLocationException ble) {
|
||||||
|
/* Should never happen ? */
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tells whether this reader is ready to be read without
|
||||||
|
* blocking for input. <code>DocumentReader</code> will
|
||||||
|
* always return true.
|
||||||
|
*
|
||||||
|
* @return <code>true</code> if the next read operation will
|
||||||
|
* return without blocking.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean ready() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resets the stream. If the stream has been marked, then attempt to
|
||||||
|
* reposition it at the mark. If the stream has not been marked, then
|
||||||
|
* move it to the beginning of the document.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void reset() {
|
||||||
|
if(mark==-1) {
|
||||||
|
position = 0;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
position = mark;
|
||||||
|
mark = -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Skips characters. This will not 'skip' past the end of the document.
|
||||||
|
*
|
||||||
|
* @param n The number of characters to skip.
|
||||||
|
* @return The number of characters actually skipped.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public long skip(long n) {
|
||||||
|
if (position+n<=document.getLength()) {
|
||||||
|
position += n;
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
long temp = position;
|
||||||
|
position = document.getLength();
|
||||||
|
return document.getLength() - temp;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Move to the specified position in the document. If <code>pos</code>
|
||||||
|
* is greater than the document's length, the stream's position is moved
|
||||||
|
* to the end of the document.
|
||||||
|
*
|
||||||
|
* @param pos The position in the document to move to.
|
||||||
|
*/
|
||||||
|
public void seek(long pos) {
|
||||||
|
position = Math.min(pos, document.getLength());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,262 @@
|
|||||||
|
/*
|
||||||
|
* 09/23/2004
|
||||||
|
*
|
||||||
|
* UnicodeReader.java - A reader for Unicode input streams that is capable of
|
||||||
|
* discerning which particular encoding is being used via the BOM.
|
||||||
|
*
|
||||||
|
* This library is distributed under a modified BSD license. See the included
|
||||||
|
* RSyntaxTextArea.License.txt file for details.
|
||||||
|
*/
|
||||||
|
package org.fife.io;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.io.PushbackInputStream;
|
||||||
|
import java.io.Reader;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A reader capable of identifying Unicode streams by their BOMs. This class
|
||||||
|
* will recognize the following encodings:
|
||||||
|
* <ul>
|
||||||
|
* <li>UTF-8
|
||||||
|
* <li>UTF-16LE
|
||||||
|
* <li>UTF-16BE
|
||||||
|
* <li>UTF-32LE
|
||||||
|
* <li>UTF-32BE
|
||||||
|
* </ul>
|
||||||
|
* If the stream is not found to be any of the above, then a default encoding
|
||||||
|
* is used for reading. The user can specify this default encoding, or a system
|
||||||
|
* default will be used.<p>
|
||||||
|
*
|
||||||
|
* For optimum performance, it is recommended that you wrap all instances of
|
||||||
|
* <code>UnicodeReader</code> with a <code>java.io.BufferedReader</code>.<p>
|
||||||
|
*
|
||||||
|
* This class is mostly ripped off from the workaround in the description of
|
||||||
|
* Java Bug 4508058.
|
||||||
|
*
|
||||||
|
* @author Robert Futrell
|
||||||
|
* @version 0.9
|
||||||
|
*/
|
||||||
|
@SuppressWarnings({ "checkstyle:magicnumber" })
|
||||||
|
public class UnicodeReader extends Reader {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The input stream from which we're really reading.
|
||||||
|
*/
|
||||||
|
private InputStreamReader internalIn = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The encoding being used. We keep our own instead of using the string
|
||||||
|
* returned by <code>java.io.InputStreamReader</code> since that class
|
||||||
|
* does not return user-friendly names.
|
||||||
|
*/
|
||||||
|
private String encoding;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The size of a BOM.
|
||||||
|
*/
|
||||||
|
private static final int BOM_SIZE = 4;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This utility constructor is here because you will usually use a
|
||||||
|
* <code>UnicodeReader</code> on files.<p>
|
||||||
|
* Creates a reader using the encoding specified by the BOM in the file;
|
||||||
|
* if there is no recognized BOM, then a system default encoding is used.
|
||||||
|
*
|
||||||
|
* @param file The file from which you want to read.
|
||||||
|
* @throws IOException If an error occurs when checking for/reading the
|
||||||
|
* BOM.
|
||||||
|
* @throws SecurityException If a security manager exists and its
|
||||||
|
* checkRead method denies read access to the file.
|
||||||
|
*/
|
||||||
|
public UnicodeReader(String file) throws IOException {
|
||||||
|
this(new File(file));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This utility constructor is here because you will usually use a
|
||||||
|
* <code>UnicodeReader</code> on files.<p>
|
||||||
|
* Creates a reader using the encoding specified by the BOM in the file;
|
||||||
|
* if there is no recognized BOM, then a system default encoding is used.
|
||||||
|
*
|
||||||
|
* @param file The file from which you want to read.
|
||||||
|
* @throws IOException If an error occurs when checking for/reading the
|
||||||
|
* BOM.
|
||||||
|
* @throws SecurityException If a security manager exists and its
|
||||||
|
* checkRead method denies read access to the file.
|
||||||
|
*/
|
||||||
|
public UnicodeReader(File file) throws IOException {
|
||||||
|
this(new FileInputStream(file));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This utility constructor is here because you will usually use a
|
||||||
|
* <code>UnicodeReader</code> on files.<p>
|
||||||
|
* Creates a reader using the encoding specified by the BOM in the file;
|
||||||
|
* if there is no recognized BOM, then a specified default encoding is
|
||||||
|
* used.
|
||||||
|
*
|
||||||
|
* @param file The file from which you want to read.
|
||||||
|
* @param defaultEncoding The encoding to use if no BOM is found. If
|
||||||
|
* this value is <code>null</code>, a system default is used.
|
||||||
|
* @throws IOException If an error occurs when checking for/reading the
|
||||||
|
* BOM.
|
||||||
|
* @throws SecurityException If a security manager exists and its
|
||||||
|
* checkRead method denies read access to the file.
|
||||||
|
*/
|
||||||
|
public UnicodeReader(File file, String defaultEncoding) throws IOException {
|
||||||
|
this(new FileInputStream(file), defaultEncoding);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a reader using the encoding specified by the BOM in the file;
|
||||||
|
* if there is no recognized BOM, then a system default encoding is used.
|
||||||
|
*
|
||||||
|
* @param in The input stream from which to read.
|
||||||
|
* @throws IOException If an error occurs when checking for/reading the
|
||||||
|
* BOM.
|
||||||
|
*/
|
||||||
|
public UnicodeReader(InputStream in) throws IOException {
|
||||||
|
this(in, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a reader using the encoding specified by the BOM in the file;
|
||||||
|
* if there is no recognized BOM, then <code>defaultEncoding</code> is
|
||||||
|
* used.
|
||||||
|
*
|
||||||
|
* @param in The input stream from which to read.
|
||||||
|
* @param defaultEncoding The encoding to use if no recognized BOM is
|
||||||
|
* found. If this value is <code>null</code>, a system default
|
||||||
|
* is used.
|
||||||
|
* @throws IOException If an error occurs when checking for/reading the
|
||||||
|
* BOM.
|
||||||
|
*/
|
||||||
|
public UnicodeReader(InputStream in, String defaultEncoding)
|
||||||
|
throws IOException {
|
||||||
|
init(in, defaultEncoding);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Closes this reader.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void close() throws IOException {
|
||||||
|
internalIn.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the encoding being used to read this input stream (i.e., the
|
||||||
|
* encoding of the file). If a BOM was recognized, then the specific
|
||||||
|
* Unicode type is returned; otherwise, either the default encoding passed
|
||||||
|
* into the constructor or the system default is returned.
|
||||||
|
*
|
||||||
|
* @return The encoding of the stream.
|
||||||
|
*/
|
||||||
|
public String getEncoding() {
|
||||||
|
return encoding;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read-ahead four bytes and check for BOM marks. Extra bytes are
|
||||||
|
* unread back to the stream, only BOM bytes are skipped.
|
||||||
|
*
|
||||||
|
* @param defaultEncoding The encoding to use if no BOM was recognized. If
|
||||||
|
* this value is <code>null</code>, then a system default is used.
|
||||||
|
* @throws IOException If an error occurs when trying to read a BOM.
|
||||||
|
*/
|
||||||
|
protected void init(InputStream in, String defaultEncoding)
|
||||||
|
throws IOException {
|
||||||
|
|
||||||
|
PushbackInputStream tempIn = new PushbackInputStream(in, BOM_SIZE);
|
||||||
|
|
||||||
|
byte[] bom = new byte[BOM_SIZE];
|
||||||
|
int n, unread;
|
||||||
|
n = tempIn.read(bom, 0, bom.length);
|
||||||
|
|
||||||
|
if ((bom[0]==(byte)0x00) && (bom[1]==(byte)0x00) &&
|
||||||
|
(bom[2]==(byte)0xFE) && (bom[3]==(byte)0xFF)) {
|
||||||
|
encoding = "UTF-32BE";
|
||||||
|
unread = n - 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
else if (n==BOM_SIZE && // Last 2 bytes are 0; could be an empty UTF-16
|
||||||
|
(bom[0]==(byte)0xFF) && (bom[1]==(byte)0xFE) &&
|
||||||
|
(bom[2]==(byte)0x00) && (bom[3]==(byte)0x00)) {
|
||||||
|
encoding = "UTF-32LE";
|
||||||
|
unread = n - 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
else if ((bom[0]==(byte)0xEF) &&
|
||||||
|
(bom[1]==(byte)0xBB) &&
|
||||||
|
(bom[2]==(byte)0xBF)) {
|
||||||
|
encoding = "UTF-8";
|
||||||
|
unread = n - 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
else if ((bom[0]==(byte)0xFE) && (bom[1] == (byte)0xFF)) {
|
||||||
|
encoding = "UTF-16BE";
|
||||||
|
unread = n - 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
else if ((bom[0]==(byte)0xFF) && (bom[1]== (byte)0xFE)) {
|
||||||
|
encoding = "UTF-16LE";
|
||||||
|
unread = n - 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
else {
|
||||||
|
// Unicode BOM mark not found, unread all bytes
|
||||||
|
encoding = defaultEncoding;
|
||||||
|
unread = n;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (unread > 0) {
|
||||||
|
tempIn.unread(bom, (n - unread), unread);
|
||||||
|
}
|
||||||
|
else if (unread < -1) {
|
||||||
|
tempIn.unread(bom, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use given encoding
|
||||||
|
if (encoding == null) {
|
||||||
|
internalIn = new InputStreamReader(tempIn);
|
||||||
|
encoding = internalIn.getEncoding(); // Get the default.
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
internalIn = new InputStreamReader(tempIn, encoding);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read characters into a portion of an array. This method will block until
|
||||||
|
* some input is available, an I/O error occurs, or the end of the stream
|
||||||
|
* is reached.
|
||||||
|
*
|
||||||
|
* @param cbuf The buffer into which to read.
|
||||||
|
* @param off The offset at which to start storing characters.
|
||||||
|
* @param len The maximum number of characters to read.
|
||||||
|
*
|
||||||
|
* @return The number of characters read, or <code>-1</code> if the end
|
||||||
|
* of the stream has been reached.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public int read(char[] cbuf, int off, int len) throws IOException {
|
||||||
|
return internalIn.read(cbuf, off, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,261 @@
|
|||||||
|
/*
|
||||||
|
* 09/24/2004
|
||||||
|
*
|
||||||
|
* UnicodeWriter.java - Writes Unicode output with the proper BOM.
|
||||||
|
*
|
||||||
|
* This library is distributed under a modified BSD license. See the included
|
||||||
|
* RSyntaxTextArea.License.txt file for details.
|
||||||
|
*/
|
||||||
|
package org.fife.io;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.io.OutputStreamWriter;
|
||||||
|
import java.io.Writer;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Writes Unicode text to an output stream. If the specified encoding is a
|
||||||
|
* Unicode, then the text is preceded by the proper Unicode BOM. If it is any
|
||||||
|
* other encoding, this class behaves just like <code>OutputStreamWriter</code>.
|
||||||
|
* This class is here because Java's <code>OutputStreamWriter</code> apparently
|
||||||
|
* doesn't believe in writing BOMs.
|
||||||
|
* <p>
|
||||||
|
*
|
||||||
|
* For optimum performance, it is recommended that you wrap all instances of
|
||||||
|
* <code>UnicodeWriter</code> with a <code>java.io.BufferedWriter</code>.
|
||||||
|
*
|
||||||
|
* @author Robert Futrell
|
||||||
|
* @version 0.7
|
||||||
|
*/
|
||||||
|
public class UnicodeWriter extends Writer {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If this system property evaluates to "<code>false</code>", ignoring
|
||||||
|
* case, files written out as UTF-8 will not have a BOM written for them.
|
||||||
|
* Otherwise (even if the property is not set), UTF-8 files will have a
|
||||||
|
* BOM written.
|
||||||
|
*/
|
||||||
|
public static final String PROPERTY_WRITE_UTF8_BOM =
|
||||||
|
"UnicodeWriter.writeUtf8BOM";
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The writer actually doing the writing.
|
||||||
|
*/
|
||||||
|
private OutputStreamWriter internalOut;
|
||||||
|
|
||||||
|
private static final byte[] UTF8_BOM = new byte[] {
|
||||||
|
(byte)0xEF,
|
||||||
|
(byte)0xBB,
|
||||||
|
(byte)0xBF
|
||||||
|
};
|
||||||
|
|
||||||
|
private static final byte[] UTF16LE_BOM = new byte[] {
|
||||||
|
(byte)0xFF,
|
||||||
|
(byte)0xFE
|
||||||
|
};
|
||||||
|
|
||||||
|
private static final byte[] UTF16BE_BOM = new byte[] {
|
||||||
|
(byte)0xFE,
|
||||||
|
(byte)0xFF
|
||||||
|
};
|
||||||
|
|
||||||
|
private static final byte[] UTF32LE_BOM = new byte[] {
|
||||||
|
(byte)0xFF,
|
||||||
|
(byte)0xFE,
|
||||||
|
(byte)0x00,
|
||||||
|
(byte)0x00
|
||||||
|
};
|
||||||
|
|
||||||
|
private static final byte[] UTF32BE_BOM = new byte[] {
|
||||||
|
(byte)0x00,
|
||||||
|
(byte)0x00,
|
||||||
|
(byte)0xFE,
|
||||||
|
(byte)0xFF
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is a utility constructor since the vast majority of the time, this
|
||||||
|
* class will be used to write Unicode files.
|
||||||
|
*
|
||||||
|
* @param fileName The file to which to write the Unicode output.
|
||||||
|
* @param encoding The encoding to use.
|
||||||
|
* @throws IOException If an IO exception occurs.
|
||||||
|
*/
|
||||||
|
public UnicodeWriter(String fileName, String encoding) throws IOException {
|
||||||
|
this(new FileOutputStream(fileName), encoding);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is a utility constructor since the vast majority of the time, this
|
||||||
|
* class will be used to write Unicode files.
|
||||||
|
*
|
||||||
|
* @param file The file to which to write the Unicode output.
|
||||||
|
* @param encoding The encoding to use.
|
||||||
|
* @throws IOException If an IO exception occurs.
|
||||||
|
*/
|
||||||
|
public UnicodeWriter(File file, String encoding) throws IOException {
|
||||||
|
this(new FileOutputStream(file), encoding);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new writer.
|
||||||
|
*
|
||||||
|
* @param out The output stream to write.
|
||||||
|
* @param encoding The encoding to use.
|
||||||
|
* @throws IOException If an IO exception occurs.
|
||||||
|
*/
|
||||||
|
public UnicodeWriter(OutputStream out, String encoding) throws IOException {
|
||||||
|
init(out, encoding);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Closes this writer.
|
||||||
|
*
|
||||||
|
* @throws IOException If an IO exception occurs.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void close() throws IOException {
|
||||||
|
internalOut.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Flushes the stream.
|
||||||
|
*
|
||||||
|
* @throws IOException If an IO exception occurs.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void flush() throws IOException {
|
||||||
|
internalOut.flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the encoding being used to write this output stream (i.e., the
|
||||||
|
* encoding of the file).
|
||||||
|
*
|
||||||
|
* @return The encoding of the stream.
|
||||||
|
*/
|
||||||
|
public String getEncoding() {
|
||||||
|
return internalOut.getEncoding();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether UTF-8 files should have a BOM in them when written.
|
||||||
|
*
|
||||||
|
* @return Whether to write a BOM for UTF-8 files.
|
||||||
|
* @see #setWriteUtf8BOM(boolean)
|
||||||
|
* @see UnicodeWriter
|
||||||
|
*/
|
||||||
|
public static boolean getWriteUtf8BOM() {
|
||||||
|
String prop = System.getProperty(PROPERTY_WRITE_UTF8_BOM);
|
||||||
|
// We default to writing the BOM, for some reason.
|
||||||
|
if (prop!=null && Boolean.valueOf(prop).equals(Boolean.FALSE)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the internal output stream and writes the BOM if the
|
||||||
|
* specified encoding is a Unicode encoding.
|
||||||
|
*
|
||||||
|
* @param out The output stream we are writing.
|
||||||
|
* @param encoding The encoding in which to write.
|
||||||
|
* @throws IOException If an I/O error occurs while writing a BOM.
|
||||||
|
*/
|
||||||
|
private void init(OutputStream out, String encoding) throws IOException {
|
||||||
|
|
||||||
|
internalOut = new OutputStreamWriter(out, encoding);
|
||||||
|
|
||||||
|
// Write the proper BOM if they specified a Unicode encoding.
|
||||||
|
// NOTE: Creating an OutputStreamWriter with encoding "UTF-16" DOES
|
||||||
|
// DOES write out the BOM; "UTF-16LE", "UTF-16BE", "UTF-32", "UTF-32LE"
|
||||||
|
// and "UTF-32BE" don't.
|
||||||
|
if ("UTF-8".equals(encoding)) {
|
||||||
|
if (getWriteUtf8BOM()) {
|
||||||
|
out.write(UTF8_BOM, 0, UTF8_BOM.length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if ("UTF-16LE".equals(encoding)) {
|
||||||
|
out.write(UTF16LE_BOM, 0, UTF16LE_BOM.length);
|
||||||
|
}
|
||||||
|
else if (/*"UTF-16".equals(encoding) || */"UTF-16BE".equals(encoding)) {
|
||||||
|
out.write(UTF16BE_BOM, 0, UTF16BE_BOM.length);
|
||||||
|
}
|
||||||
|
else if ("UTF-32LE".equals(encoding)) {
|
||||||
|
out.write(UTF32LE_BOM, 0, UTF32LE_BOM.length);
|
||||||
|
}
|
||||||
|
else if ("UTF-32".equals(encoding) || "UTF-32BE".equals(encoding)) {
|
||||||
|
out.write(UTF32BE_BOM, 0, UTF32BE_BOM.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets whether UTF-8 files should have a BOM written in them.
|
||||||
|
*
|
||||||
|
* @param write Whether to write a BOM.
|
||||||
|
* @see #getWriteUtf8BOM()
|
||||||
|
* @see UnicodeWriter
|
||||||
|
*/
|
||||||
|
public static void setWriteUtf8BOM(boolean write) {
|
||||||
|
System.setProperty(UnicodeWriter.PROPERTY_WRITE_UTF8_BOM,
|
||||||
|
Boolean.toString(write));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Writes a portion of an array of characters.
|
||||||
|
*
|
||||||
|
* @param cbuf The buffer of characters.
|
||||||
|
* @param off The offset from which to start writing characters.
|
||||||
|
* @param len The number of characters to write.
|
||||||
|
* @throws IOException If an I/O error occurs.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void write(char[] cbuf, int off, int len) throws IOException {
|
||||||
|
internalOut.write(cbuf, off, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Writes a single character.
|
||||||
|
*
|
||||||
|
* @param c An integer specifying the character to write.
|
||||||
|
* @throws IOException If an IO error occurs.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void write(int c) throws IOException {
|
||||||
|
internalOut.write(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Writes a portion of a string.
|
||||||
|
*
|
||||||
|
* @param str The string from which to write.
|
||||||
|
* @param off The offset from which to start writing characters.
|
||||||
|
* @param len The number of characters to write.
|
||||||
|
* @throws IOException If an IO error occurs.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void write(String str, int off, int len) throws IOException {
|
||||||
|
internalOut.write(str, off, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,558 @@
|
|||||||
|
/*
|
||||||
|
* 11/14/2003
|
||||||
|
*
|
||||||
|
* RPrintUtilities.java - A collection of static methods useful for printing
|
||||||
|
* text from Swing text components.
|
||||||
|
*
|
||||||
|
* This library is distributed under a modified BSD license. See the included
|
||||||
|
* RSyntaxTextArea.License.txt file for details.
|
||||||
|
*/
|
||||||
|
package org.fife.print;
|
||||||
|
|
||||||
|
import java.awt.Color;
|
||||||
|
import java.awt.Font;
|
||||||
|
import java.awt.FontMetrics;
|
||||||
|
import java.awt.Graphics;
|
||||||
|
import java.awt.print.PageFormat;
|
||||||
|
import java.awt.print.Printable;
|
||||||
|
|
||||||
|
import javax.swing.text.BadLocationException;
|
||||||
|
import javax.swing.text.Document;
|
||||||
|
import javax.swing.text.Element;
|
||||||
|
import javax.swing.text.JTextComponent;
|
||||||
|
import javax.swing.text.Segment;
|
||||||
|
import javax.swing.text.TabExpander;
|
||||||
|
import javax.swing.text.Utilities;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A collection of static methods useful for printing text from Swing text components.
|
||||||
|
*
|
||||||
|
* @author Robert Futrell
|
||||||
|
* @version 1.0
|
||||||
|
*/
|
||||||
|
public abstract class RPrintUtilities {
|
||||||
|
|
||||||
|
private static int currentDocLineNumber; // The line number in the document we are currently on.
|
||||||
|
private static int numDocLines; // The number of lines in the current document.
|
||||||
|
private static Element rootElement; // The first Element (line) in the current document.
|
||||||
|
|
||||||
|
// The characters at which to break a line if implementing word wrap.
|
||||||
|
private static final char [] BREAK_CHARS = { ' ', '\t', ',', '.', ';', '?', '!' };
|
||||||
|
|
||||||
|
// These variables are 'global' because RPrintTabExpander uses them.
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The x-offset (for the page margin) when printing.
|
||||||
|
*/
|
||||||
|
private static int xOffset;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The length of a tab, in spaces.
|
||||||
|
*/
|
||||||
|
private static int tabSizeInSpaces;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The metrics of the font currently being used to print.
|
||||||
|
*/
|
||||||
|
private static FontMetrics fm;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the position closest to, but before, position <code>maxCharsPerLine</code> in
|
||||||
|
* <code>line</code> of one of the chars in <code>breakChars</code>, or simply returns
|
||||||
|
* <code>maxCharsPerLine-1</code> if none of the <code>breakChars</code> comes before
|
||||||
|
* that position. This position represents the logical line break for this <code>java.lang.String</code>
|
||||||
|
* if it is being printed in a monospaced font when lines can only be <code>maxCharsPerLine</code>
|
||||||
|
* characters long.
|
||||||
|
*
|
||||||
|
* @param line The text being printed.
|
||||||
|
* @param maxCharsPerLine Only up-to this many characters from
|
||||||
|
* <code>line</code> can be printed on one line.
|
||||||
|
* @return The logical position at which to stop printing <code>line</code>
|
||||||
|
* to simulate word wrap.
|
||||||
|
*/
|
||||||
|
private static int getLineBreakPoint(String line, final int maxCharsPerLine) {
|
||||||
|
|
||||||
|
int breakPoint = -1;
|
||||||
|
for (int i=0; i<BREAK_CHARS.length; i++) {
|
||||||
|
int breakCharPos = line.lastIndexOf(BREAK_CHARS[i], maxCharsPerLine-1);
|
||||||
|
if (breakCharPos > breakPoint) {
|
||||||
|
breakPoint = breakCharPos;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (breakPoint==-1 ? maxCharsPerLine-1 : breakPoint);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prints a <code>Document</code> using a monospaced font, and does no word wrapping (ie,
|
||||||
|
* words will wrap mid-word to the next line). This method is expected to be called from
|
||||||
|
* Printable 'print(Graphics g)' functions.
|
||||||
|
*
|
||||||
|
* @param g The graphics context to write to.
|
||||||
|
* @param doc The <code>javax.swing.text.Document</code> to print.
|
||||||
|
* @param fontSize the point size to use for the monospaced font.
|
||||||
|
* @param pageIndex The page number to print.
|
||||||
|
* @param pageFormat The format to print the page with.
|
||||||
|
* @param tabSize The number of spaces to expand tabs to.
|
||||||
|
*
|
||||||
|
* @see #printDocumentMonospacedWordWrap
|
||||||
|
*/
|
||||||
|
public static int printDocumentMonospaced(Graphics g, Document doc, int fontSize, int pageIndex,
|
||||||
|
PageFormat pageFormat, int tabSize) {
|
||||||
|
|
||||||
|
g.setColor(Color.BLACK);
|
||||||
|
g.setFont(new Font("Monospaced", Font.PLAIN, fontSize));
|
||||||
|
|
||||||
|
// Initialize our static variables (these are used by our tab expander below).
|
||||||
|
tabSizeInSpaces = tabSize;
|
||||||
|
fm = g.getFontMetrics();
|
||||||
|
|
||||||
|
// Create our tab expander.
|
||||||
|
//RPrintTabExpander tabExpander = new RPrintTabExpander();
|
||||||
|
|
||||||
|
// Get width and height of characters in this monospaced font.
|
||||||
|
int fontWidth = fm.charWidth('w'); // Any character will do as font is monospaced.
|
||||||
|
int fontHeight = fm.getHeight();
|
||||||
|
|
||||||
|
int maxCharsPerLine = (int)pageFormat.getImageableWidth() / fontWidth;
|
||||||
|
int maxLinesPerPage = (int)pageFormat.getImageableHeight() / fontHeight;
|
||||||
|
|
||||||
|
final int startingLineNumber = maxLinesPerPage * pageIndex;
|
||||||
|
|
||||||
|
// The (x,y) coordinate to print at (in pixels, not characters).
|
||||||
|
// Since y is the baseline of where we'll start printing (not the top-left
|
||||||
|
// corner), we offset it by the font's ascent ( + 1 just for good measure).
|
||||||
|
xOffset = (int)pageFormat.getImageableX();
|
||||||
|
int y = (int)pageFormat.getImageableY() + fm.getAscent() + 1;
|
||||||
|
|
||||||
|
// A counter to keep track of the number of lines that WOULD HAVE been
|
||||||
|
// printed if we were printing all lines.
|
||||||
|
int numPrintedLines = 0;
|
||||||
|
|
||||||
|
// Keep going while there are more lines in the document.
|
||||||
|
currentDocLineNumber = 0; // The line number of the document we're currently on.
|
||||||
|
rootElement = doc.getDefaultRootElement(); // To shorten accesses in our loop.
|
||||||
|
numDocLines = rootElement.getElementCount(); // The number of lines in our document.
|
||||||
|
while (currentDocLineNumber<numDocLines) {
|
||||||
|
|
||||||
|
// Get the line we are going to print.
|
||||||
|
String curLineString;
|
||||||
|
Element currentLine = rootElement.getElement(currentDocLineNumber);
|
||||||
|
int startOffs = currentLine.getStartOffset();
|
||||||
|
try {
|
||||||
|
curLineString = doc.getText(startOffs, currentLine.getEndOffset()-startOffs);
|
||||||
|
} catch (BadLocationException ble) { // Never happens
|
||||||
|
ble.printStackTrace();
|
||||||
|
return Printable.NO_SUCH_PAGE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get rid of newlines, because they end up as boxes if you don't; this is a monospaced font.
|
||||||
|
curLineString = curLineString.replaceAll("\n", "");
|
||||||
|
|
||||||
|
// Replace tabs with how many spaces they should be.
|
||||||
|
if (tabSizeInSpaces == 0) {
|
||||||
|
curLineString = curLineString.replaceAll("\t", "");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
int tabIndex = curLineString.indexOf('\t');
|
||||||
|
while (tabIndex > -1) {
|
||||||
|
int spacesNeeded = tabSizeInSpaces - (tabIndex % tabSizeInSpaces);
|
||||||
|
StringBuilder stringBuilder = new StringBuilder();
|
||||||
|
for (int i=0; i<spacesNeeded; i++) {
|
||||||
|
stringBuilder.append(" ");
|
||||||
|
}
|
||||||
|
// Note that "\t" is actually a regex for this method.
|
||||||
|
curLineString = curLineString.replaceFirst("\t", stringBuilder.toString());
|
||||||
|
tabIndex = curLineString.indexOf('\t');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If this document line is too long to fit on one printed line on the page,
|
||||||
|
// break it up into multpile lines.
|
||||||
|
while (curLineString.length() > maxCharsPerLine) {
|
||||||
|
|
||||||
|
numPrintedLines++;
|
||||||
|
if (numPrintedLines > startingLineNumber) {
|
||||||
|
g.drawString(curLineString.substring(0,maxCharsPerLine), xOffset,y);
|
||||||
|
y += fontHeight;
|
||||||
|
if (numPrintedLines==startingLineNumber+maxLinesPerPage) {
|
||||||
|
return Printable.PAGE_EXISTS;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
curLineString = curLineString.substring(maxCharsPerLine, curLineString.length());
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
currentDocLineNumber += 1; // We have printed one more line from the document.
|
||||||
|
|
||||||
|
numPrintedLines++;
|
||||||
|
if (numPrintedLines>startingLineNumber) {
|
||||||
|
g.drawString(curLineString, xOffset,y);
|
||||||
|
y += fontHeight;
|
||||||
|
if (numPrintedLines==startingLineNumber+maxLinesPerPage) {
|
||||||
|
return Printable.PAGE_EXISTS;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now, the whole document has been "printed." Decide if this page had any text on it or not.
|
||||||
|
if (numPrintedLines > startingLineNumber) {
|
||||||
|
return Printable.PAGE_EXISTS;
|
||||||
|
}
|
||||||
|
return Printable.NO_SUCH_PAGE;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prints a <code>Document</code> using a monospaced font, word wrapping on
|
||||||
|
* the characters ' ', '\t', '\n', ',', '.', and ';'. This method is
|
||||||
|
* expected to be called from Printable 'print(Graphics g)' functions.
|
||||||
|
*
|
||||||
|
* @param g The graphics context to write to.
|
||||||
|
* @param doc The <code>javax.swing.text.Document</code> to print.
|
||||||
|
* @param fontSize the point size to use for the monospaced font.
|
||||||
|
* @param pageIndex The page number to print.
|
||||||
|
* @param pageFormat The format to print the page with.
|
||||||
|
* @param tabSize The number of spaces to expand tabs to.
|
||||||
|
*
|
||||||
|
* @see #printDocumentMonospaced
|
||||||
|
*/
|
||||||
|
public static int printDocumentMonospacedWordWrap(Graphics g, Document doc,
|
||||||
|
int fontSize, int pageIndex,
|
||||||
|
PageFormat pageFormat, int tabSize) {
|
||||||
|
|
||||||
|
g.setColor(Color.BLACK);
|
||||||
|
g.setFont(new Font("Monospaced", Font.PLAIN, fontSize));
|
||||||
|
|
||||||
|
// Initialize our static variables (these are used by our tab expander below).
|
||||||
|
tabSizeInSpaces = tabSize;
|
||||||
|
fm = g.getFontMetrics();
|
||||||
|
|
||||||
|
// Create our tab expander.
|
||||||
|
//RPrintTabExpander tabExpander = new RPrintTabExpander();
|
||||||
|
|
||||||
|
// Get width and height of characters in this monospaced font.
|
||||||
|
int fontWidth = fm.charWidth('w'); // Any character will do here, since font is monospaced.
|
||||||
|
int fontHeight = fm.getHeight();
|
||||||
|
|
||||||
|
int maxCharsPerLine = (int)pageFormat.getImageableWidth() / fontWidth;
|
||||||
|
int maxLinesPerPage = (int)pageFormat.getImageableHeight() / fontHeight;
|
||||||
|
|
||||||
|
final int startingLineNumber = maxLinesPerPage * pageIndex;
|
||||||
|
|
||||||
|
// The (x,y) coordinate to print at (in pixels, not characters).
|
||||||
|
// Since y is the baseline of where we'll start printing (not the top-left
|
||||||
|
// corner), we offset it by the font's ascent ( + 1 just for good measure).
|
||||||
|
xOffset = (int)pageFormat.getImageableX();
|
||||||
|
int y = (int)pageFormat.getImageableY() + fm.getAscent() + 1;
|
||||||
|
|
||||||
|
// A counter to keep track of the number of lines that WOULD HAVE been
|
||||||
|
// printed if we were printing all lines.
|
||||||
|
int numPrintedLines = 0;
|
||||||
|
|
||||||
|
// Keep going while there are more lines in the document.
|
||||||
|
currentDocLineNumber = 0; // The line number of the document we're currently on.
|
||||||
|
rootElement = doc.getDefaultRootElement(); // To shorten accesses in our loop.
|
||||||
|
numDocLines = rootElement.getElementCount(); // The number of lines in our document.
|
||||||
|
while (currentDocLineNumber<numDocLines) {
|
||||||
|
|
||||||
|
// Get the line we are going to print.
|
||||||
|
String curLineString;
|
||||||
|
Element currentLine = rootElement.getElement(currentDocLineNumber);
|
||||||
|
int startOffs = currentLine.getStartOffset();
|
||||||
|
try {
|
||||||
|
curLineString = doc.getText(startOffs, currentLine.getEndOffset()-startOffs);
|
||||||
|
} catch (BadLocationException ble) { // Never happens
|
||||||
|
ble.printStackTrace();
|
||||||
|
return Printable.NO_SUCH_PAGE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove newlines, because they end up as boxes if you don't; this is a monospaced font.
|
||||||
|
curLineString = curLineString.replaceAll("\n", "");
|
||||||
|
|
||||||
|
// Replace tabs with how many spaces they should be.
|
||||||
|
if (tabSizeInSpaces == 0) {
|
||||||
|
curLineString = curLineString.replaceAll("\t", "");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
int tabIndex = curLineString.indexOf('\t');
|
||||||
|
while (tabIndex > -1) {
|
||||||
|
int spacesNeeded = tabSizeInSpaces - (tabIndex % tabSizeInSpaces);
|
||||||
|
StringBuilder stringBuilder = new StringBuilder();
|
||||||
|
for (int i=0; i<spacesNeeded; i++) {
|
||||||
|
stringBuilder.append(" ");
|
||||||
|
}
|
||||||
|
// Note that "\t" is actually a regex for this method.
|
||||||
|
curLineString = curLineString.replaceFirst("\t", stringBuilder.toString());
|
||||||
|
tabIndex = curLineString.indexOf('\t');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If this document line is too long to fit on one printed line on the page,
|
||||||
|
// break it up into multpile lines.
|
||||||
|
while (curLineString.length() > maxCharsPerLine) {
|
||||||
|
|
||||||
|
int breakPoint = getLineBreakPoint(curLineString, maxCharsPerLine) + 1;
|
||||||
|
|
||||||
|
numPrintedLines++;
|
||||||
|
if (numPrintedLines > startingLineNumber) {
|
||||||
|
g.drawString(curLineString.substring(0,breakPoint), xOffset,y);
|
||||||
|
y += fontHeight;
|
||||||
|
if (numPrintedLines==startingLineNumber+maxLinesPerPage) {
|
||||||
|
return Printable.PAGE_EXISTS;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
curLineString = curLineString.substring(breakPoint, curLineString.length());
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
currentDocLineNumber += 1; // We have printed one more line from the document.
|
||||||
|
|
||||||
|
numPrintedLines++;
|
||||||
|
if (numPrintedLines>startingLineNumber) {
|
||||||
|
g.drawString(curLineString, xOffset,y);
|
||||||
|
y += fontHeight;
|
||||||
|
if (numPrintedLines==startingLineNumber+maxLinesPerPage) {
|
||||||
|
return Printable.PAGE_EXISTS;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now, the whole document has been "printed." Decide if this page had any text on it or not.
|
||||||
|
if (numPrintedLines > startingLineNumber) {
|
||||||
|
return Printable.PAGE_EXISTS;
|
||||||
|
}
|
||||||
|
return Printable.NO_SUCH_PAGE;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prints a <code>Document</code> using the specified font, word wrapping
|
||||||
|
* on the characters ' ', '\t', '\n', ',', '.', and ';'. This method is
|
||||||
|
* expected to be called from Printable 'print(Graphics g)' functions.
|
||||||
|
*
|
||||||
|
* @param g The graphics context to write to.
|
||||||
|
* @param textComponent The <code>javax.swing.text.JTextComponent</code>
|
||||||
|
* whose text you're printing.
|
||||||
|
* @param font The font to use for printing. If <code>null</code>, then
|
||||||
|
* <code>textComponent</code>'s font is used.
|
||||||
|
* @param pageIndex The page number to print.
|
||||||
|
* @param pageFormat The format to print the page with.
|
||||||
|
* @param tabSize The number of spaces to convert tabs to.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public static int printDocumentWordWrap(Graphics g, JTextComponent textComponent,
|
||||||
|
Font font, int pageIndex,
|
||||||
|
PageFormat pageFormat,
|
||||||
|
int tabSize) {
|
||||||
|
|
||||||
|
// Initialize our graphics object.
|
||||||
|
g.setColor(Color.BLACK);
|
||||||
|
g.setFont(font!=null ? font : textComponent.getFont());
|
||||||
|
|
||||||
|
// Initialize our static variables (these are used by our tab expander below).
|
||||||
|
tabSizeInSpaces = tabSize;
|
||||||
|
fm = g.getFontMetrics();
|
||||||
|
int fontHeight = fm.getHeight();
|
||||||
|
|
||||||
|
final int lineLengthInPixels = (int)pageFormat.getImageableWidth();
|
||||||
|
final int maxLinesPerPage = (int)pageFormat.getImageableHeight() / fontHeight;
|
||||||
|
|
||||||
|
final int startingLineNumber = maxLinesPerPage * pageIndex;
|
||||||
|
|
||||||
|
// Create our tab expander.
|
||||||
|
RPrintTabExpander tabExpander = new RPrintTabExpander();
|
||||||
|
|
||||||
|
// The (x,y) coordinate to print at (in pixels, not characters).
|
||||||
|
// Since y is the baseline of where we'll start printing (not the top-left
|
||||||
|
// corner), we offset it by the font's ascent ( + 1 just for good measure).
|
||||||
|
xOffset = (int)pageFormat.getImageableX();
|
||||||
|
int y = (int)pageFormat.getImageableY() + fm.getAscent() + 1;
|
||||||
|
|
||||||
|
// A counter to keep track of the number of lines that WOULD HAVE been
|
||||||
|
// printed if we were printing all lines.
|
||||||
|
int numPrintedLines = 0;
|
||||||
|
|
||||||
|
// Keep going while there are more lines in the document.
|
||||||
|
Document doc = textComponent.getDocument();
|
||||||
|
rootElement = doc.getDefaultRootElement();
|
||||||
|
numDocLines = rootElement.getElementCount(); // The number of lines in our document.
|
||||||
|
currentDocLineNumber = 0; // The line number of the document we're currently on.
|
||||||
|
int startingOffset = 0; // Used when a line is so long it has to be wrapped.
|
||||||
|
while (currentDocLineNumber<numDocLines) {
|
||||||
|
|
||||||
|
Segment currentLineSeg = new Segment();
|
||||||
|
|
||||||
|
// Get the current line (as an Element), and its starting and ending offset in doc.
|
||||||
|
Element currentLine = rootElement.getElement(currentDocLineNumber);
|
||||||
|
int currentLineStart = currentLine.getStartOffset();
|
||||||
|
int currentLineEnd = currentLine.getEndOffset();
|
||||||
|
|
||||||
|
// Put the chars of this line in currentLineSeg, but only starting at our desired offset
|
||||||
|
// (because this line may be the second part of a wrapped line, so we'd start after the part
|
||||||
|
// that has already been printed).
|
||||||
|
try {
|
||||||
|
doc.getText(currentLineStart+startingOffset, currentLineEnd-(currentLineStart+startingOffset),
|
||||||
|
currentLineSeg);
|
||||||
|
} catch (BadLocationException ble) {
|
||||||
|
System.err.println("BadLocationException in print (where there shouldn't be one!): " + ble);
|
||||||
|
return Printable.NO_SUCH_PAGE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove any spaces and/or tabs from the end of the segment (would cause problems if you left 'em).
|
||||||
|
currentLineSeg = removeEndingWhitespace(currentLineSeg);
|
||||||
|
|
||||||
|
// Figger out how long the line is, in pixels.
|
||||||
|
int currentLineLengthInPixels = Utilities.getTabbedTextWidth(currentLineSeg, fm, 0, tabExpander, 0);
|
||||||
|
|
||||||
|
//System.err.println("'" + currentLineSeg + "' - " + currentLineLengthInPixels + "/" + LINE_LENGTH_IN_PIXELS);
|
||||||
|
// If it'll fit by itself on a printed line, great.
|
||||||
|
if (currentLineLengthInPixels <= lineLengthInPixels) {
|
||||||
|
currentDocLineNumber += 1; // We (will) have printed one more line from the document.
|
||||||
|
startingOffset = 0; // Start at the first character in the new document line.
|
||||||
|
}
|
||||||
|
|
||||||
|
// If it won't fit on a printed line by itself (i.e., it needs to be wrapped)...
|
||||||
|
else {
|
||||||
|
|
||||||
|
// Loop while the current line is too long to fit on a printed line.
|
||||||
|
int currentPos = -1;
|
||||||
|
while (currentLineLengthInPixels > lineLengthInPixels) {
|
||||||
|
|
||||||
|
//System.err.println("'" + currentLineSeg + "' - " + currentLineLengthInPixels + "/" + LINE_LENGTH_IN_PIXELS);
|
||||||
|
|
||||||
|
// Remove any spaces and/or tabs from the end of the segment (would cause problems if you left 'em).
|
||||||
|
currentLineSeg = removeEndingWhitespace(currentLineSeg);
|
||||||
|
|
||||||
|
// currentPos will be the last position in the current text of a "line break character."
|
||||||
|
currentPos = -1;
|
||||||
|
String currentLineString = currentLineSeg.toString();
|
||||||
|
for (int i=0; i<BREAK_CHARS.length; i++) {
|
||||||
|
// "+1" below so we include the character on the line.
|
||||||
|
int pos = currentLineString.lastIndexOf(BREAK_CHARS[i]) + 1;
|
||||||
|
//if (pos>-1 && pos>currentPos)
|
||||||
|
// currentPos = pos;
|
||||||
|
if (pos>0 && pos>currentPos && pos!=currentLineString.length()) {
|
||||||
|
currentPos = pos;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we didn't find a line break character, we'll simply break the line at
|
||||||
|
// the last character that fits on a printed line.
|
||||||
|
// So here, we set currentPos to be the position of the last character that fits
|
||||||
|
// on the current printed line.
|
||||||
|
if (currentPos == -1) {
|
||||||
|
|
||||||
|
// Fix currentLineSeg so that it contains exactly enough text to fit in
|
||||||
|
// LINE_LENGTH_IN_PIXELS pixels...
|
||||||
|
currentPos = 0;
|
||||||
|
do {
|
||||||
|
currentPos++;
|
||||||
|
try {
|
||||||
|
doc.getText(currentLineStart+startingOffset, currentPos, currentLineSeg);
|
||||||
|
} catch (BadLocationException ble) {
|
||||||
|
System.err.println(ble);
|
||||||
|
return Printable.NO_SUCH_PAGE;
|
||||||
|
}
|
||||||
|
currentLineLengthInPixels = Utilities.
|
||||||
|
getTabbedTextWidth(currentLineSeg, fm, 0, tabExpander, 0);
|
||||||
|
} while (currentLineLengthInPixels <= lineLengthInPixels);
|
||||||
|
currentPos--;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
doc.getText((currentLineStart+startingOffset), currentPos, currentLineSeg);
|
||||||
|
} catch (BadLocationException ble) {
|
||||||
|
System.err.println("BadLocationException in print (a):");
|
||||||
|
System.err.println("==> currentLineStart: " + currentLineStart +
|
||||||
|
"; startingOffset: " + startingOffset + "; currentPos: " + currentPos);
|
||||||
|
System.err.println("==> Range: " + (currentLineStart+startingOffset) + " - " +
|
||||||
|
(currentLineStart+startingOffset+currentPos));
|
||||||
|
ble.printStackTrace();
|
||||||
|
return Printable.NO_SUCH_PAGE;
|
||||||
|
}
|
||||||
|
|
||||||
|
currentLineLengthInPixels = Utilities.getTabbedTextWidth(currentLineSeg, fm, 0, tabExpander, 0);
|
||||||
|
} // End of while (currentLineLengthInPixels > LINE_LENGTH_IN_PIXELS).
|
||||||
|
|
||||||
|
startingOffset += currentPos; // Where to start (offset from line's start), since this line wraps.
|
||||||
|
|
||||||
|
} // End of else.
|
||||||
|
|
||||||
|
numPrintedLines++;
|
||||||
|
if (numPrintedLines>startingLineNumber) {
|
||||||
|
//g.drawString(currentLineSeg.toString(), xOffset,y);
|
||||||
|
Utilities.drawTabbedText(currentLineSeg, xOffset,y, g, tabExpander, 0);
|
||||||
|
y += fontHeight;
|
||||||
|
if (numPrintedLines==startingLineNumber+maxLinesPerPage) {
|
||||||
|
return Printable.PAGE_EXISTS;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now, the whole document has been "printed." Decide if this page had any text on it or not.
|
||||||
|
if (numPrintedLines > startingLineNumber) {
|
||||||
|
return Printable.PAGE_EXISTS;
|
||||||
|
}
|
||||||
|
return Printable.NO_SUCH_PAGE;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes any spaces or tabs from the end of the segment.
|
||||||
|
*
|
||||||
|
* @param segment The segment from which to remove tailing whitespace.
|
||||||
|
* @return <code>segment</code> with trailing whitespace removed.
|
||||||
|
*/
|
||||||
|
private static Segment removeEndingWhitespace(Segment segment) {
|
||||||
|
int toTrim = 0;
|
||||||
|
char currentChar = segment.setIndex(segment.getEndIndex()-1);
|
||||||
|
while ((currentChar==' ' || currentChar=='\t') && currentChar!=Segment.DONE) {
|
||||||
|
toTrim++;
|
||||||
|
currentChar = segment.previous();
|
||||||
|
}
|
||||||
|
String stringVal = segment.toString();
|
||||||
|
String newStringVal = stringVal.substring(0,stringVal.length()-toTrim);
|
||||||
|
return new Segment(newStringVal.toCharArray(), 0, newStringVal.length());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A tab expander for the document currently being printed with the
|
||||||
|
* font being used for the printing.
|
||||||
|
*/
|
||||||
|
private static class RPrintTabExpander implements TabExpander {
|
||||||
|
|
||||||
|
RPrintTabExpander() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public float nextTabStop(float x, int tabOffset) {
|
||||||
|
if (tabSizeInSpaces == 0) {
|
||||||
|
return x;
|
||||||
|
}
|
||||||
|
int tabSizeInPixels = tabSizeInSpaces * fm.charWidth(' ');
|
||||||
|
int ntabs = (((int) x) - xOffset) / tabSizeInPixels;
|
||||||
|
return xOffset + ((ntabs + 1f) * tabSizeInPixels);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,185 @@
|
|||||||
|
/*
|
||||||
|
* 12/21/2008
|
||||||
|
*
|
||||||
|
* AbstractCompletion.java - Base class for possible completions.
|
||||||
|
*
|
||||||
|
* This library is distributed under a modified BSD license. See the included
|
||||||
|
* AutoComplete.License.txt file for details.
|
||||||
|
*/
|
||||||
|
package org.fife.ui.autocomplete;
|
||||||
|
|
||||||
|
import javax.swing.Icon;
|
||||||
|
import javax.swing.text.JTextComponent;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base class for possible completions. Most, if not all, {@link Completion}
|
||||||
|
* implementations can extend this class. It remembers the
|
||||||
|
* <tt>CompletionProvider</tt> that returns this completion, and also implements
|
||||||
|
* <tt>Comparable</tt>, allowing such completions to be compared
|
||||||
|
* lexicographically (ignoring case).<p>
|
||||||
|
*
|
||||||
|
* This implementation assumes the input text and replacement text are the
|
||||||
|
* same value. It also returns the input text from its {@link #toString()}
|
||||||
|
* method (which is what <code>DefaultListCellRenderer</code> uses to render
|
||||||
|
* objects). Subclasses that wish to override any of this behavior can simply
|
||||||
|
* override the corresponding method(s) needed to do so.
|
||||||
|
*
|
||||||
|
* @author Robert Futrell
|
||||||
|
* @version 1.0
|
||||||
|
*/
|
||||||
|
public abstract class AbstractCompletion implements Completion {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The provider that created this completion;
|
||||||
|
*/
|
||||||
|
private CompletionProvider provider;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The icon to use for this completion.
|
||||||
|
*/
|
||||||
|
private Icon icon;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The relevance of this completion. Completion instances with higher
|
||||||
|
* "relevance" values are inserted higher into the list of possible
|
||||||
|
* completions than those with lower values. Completion instances with
|
||||||
|
* equal relevance values are sorted alphabetically.
|
||||||
|
*/
|
||||||
|
private int relevance;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
*
|
||||||
|
* @param provider The provider that created this completion.
|
||||||
|
*/
|
||||||
|
protected AbstractCompletion(CompletionProvider provider) {
|
||||||
|
this.provider = provider;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
*
|
||||||
|
* @param provider The provider that created this completion.
|
||||||
|
* @param icon The icon for this completion.
|
||||||
|
*/
|
||||||
|
protected AbstractCompletion(CompletionProvider provider, Icon icon) {
|
||||||
|
this(provider);
|
||||||
|
setIcon(icon);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public int compareTo(Completion c2) {
|
||||||
|
if (c2==this) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
else if (c2!=null) {
|
||||||
|
return toString().compareToIgnoreCase(c2.toString());
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String getAlreadyEntered(JTextComponent comp) {
|
||||||
|
return provider.getAlreadyEnteredText(comp);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Icon getIcon() {
|
||||||
|
return icon;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the text the user has to (start) typing for this completion
|
||||||
|
* to be offered. The default implementation simply returns
|
||||||
|
* {@link #getReplacementText()}.
|
||||||
|
*
|
||||||
|
* @return The text the user has to (start) typing for this completion.
|
||||||
|
* @see #getReplacementText()
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String getInputText() {
|
||||||
|
return getReplacementText();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public CompletionProvider getProvider() {
|
||||||
|
return provider;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public int getRelevance() {
|
||||||
|
return relevance;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The default implementation returns <code>null</code>. Subclasses
|
||||||
|
* can override this method.
|
||||||
|
*
|
||||||
|
* @return The tool tip text.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String getToolTipText() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the icon to use for this completion.
|
||||||
|
*
|
||||||
|
* @param icon The icon to use.
|
||||||
|
* @see #getIcon()
|
||||||
|
*/
|
||||||
|
public void setIcon(Icon icon) {
|
||||||
|
this.icon = icon;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the relevance of this completion.
|
||||||
|
*
|
||||||
|
* @param relevance The new relevance of this completion.
|
||||||
|
* @see #getRelevance()
|
||||||
|
*/
|
||||||
|
public void setRelevance(int relevance) {
|
||||||
|
this.relevance = relevance;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a string representation of this completion. The default
|
||||||
|
* implementation returns {@link #getInputText()}.
|
||||||
|
*
|
||||||
|
* @return A string representation of this completion.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return getInputText();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,249 @@
|
|||||||
|
/*
|
||||||
|
* 12/21/2008
|
||||||
|
*
|
||||||
|
* AbstractCompletionProvider.java - Base class for completion providers.
|
||||||
|
*
|
||||||
|
* This library is distributed under a modified BSD license. See the included
|
||||||
|
* AutoComplete.License.txt file for details.
|
||||||
|
*/
|
||||||
|
package org.fife.ui.autocomplete;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import javax.swing.text.JTextComponent;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A base class for completion providers. {@link Completion}s are kept in
|
||||||
|
* a sorted list. To get the list of completions that match a given input,
|
||||||
|
* a binary search is done to find the first matching completion, then all
|
||||||
|
* succeeding completions that also match are also returned.
|
||||||
|
*
|
||||||
|
* @author Robert Futrell
|
||||||
|
* @version 1.0
|
||||||
|
*/
|
||||||
|
public abstract class AbstractCompletionProvider
|
||||||
|
extends CompletionProviderBase {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The completions this provider is aware of. Subclasses should ensure
|
||||||
|
* that this list is sorted alphabetically (case-insensitively).
|
||||||
|
*/
|
||||||
|
protected List<Completion> completions;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compares a {@link Completion} against a String.
|
||||||
|
*/
|
||||||
|
protected CaseInsensitiveComparator comparator;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
*/
|
||||||
|
public AbstractCompletionProvider() {
|
||||||
|
comparator = new CaseInsensitiveComparator();
|
||||||
|
clearParameterizedCompletionParams();
|
||||||
|
completions = new ArrayList<Completion>();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a single completion to this provider. If you are adding multiple
|
||||||
|
* completions to this provider, for efficiency reasons please consider
|
||||||
|
* using {@link #addCompletions(List)} instead.
|
||||||
|
*
|
||||||
|
* @param c The completion to add.
|
||||||
|
* @throws IllegalArgumentException If the completion's provider isn't
|
||||||
|
* this <tt>CompletionProvider</tt>.
|
||||||
|
* @see #addCompletions(List)
|
||||||
|
* @see #removeCompletion(Completion)
|
||||||
|
* @see #clear()
|
||||||
|
*/
|
||||||
|
public void addCompletion(Completion c) {
|
||||||
|
checkProviderAndAdd(c);
|
||||||
|
Collections.sort(completions);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds {@link Completion}s to this provider.
|
||||||
|
*
|
||||||
|
* @param completions The completions to add. This cannot be
|
||||||
|
* <code>null</code>.
|
||||||
|
* @throws IllegalArgumentException If a completion's provider isn't
|
||||||
|
* this <tt>CompletionProvider</tt>.
|
||||||
|
* @see #addCompletion(Completion)
|
||||||
|
* @see #removeCompletion(Completion)
|
||||||
|
* @see #clear()
|
||||||
|
*/
|
||||||
|
public void addCompletions(List<Completion> completions) {
|
||||||
|
//this.completions.addAll(completions);
|
||||||
|
for (Completion c : completions) {
|
||||||
|
checkProviderAndAdd(c);
|
||||||
|
}
|
||||||
|
Collections.sort(this.completions);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds simple completions for a list of words.
|
||||||
|
*
|
||||||
|
* @param words The words.
|
||||||
|
* @see BasicCompletion
|
||||||
|
*/
|
||||||
|
protected void addWordCompletions(String[] words) {
|
||||||
|
int count = words==null ? 0 : words.length;
|
||||||
|
for (int i=0; i<count; i++) {
|
||||||
|
completions.add(new BasicCompletion(this, words[i]));
|
||||||
|
}
|
||||||
|
Collections.sort(completions);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected void checkProviderAndAdd(Completion c) {
|
||||||
|
if (c.getProvider()!=this) {
|
||||||
|
throw new IllegalArgumentException("Invalid CompletionProvider");
|
||||||
|
}
|
||||||
|
completions.add(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes all completions from this provider. This does not affect
|
||||||
|
* the parent <tt>CompletionProvider</tt>, if there is one.
|
||||||
|
*
|
||||||
|
* @see #addCompletion(Completion)
|
||||||
|
* @see #addCompletions(List)
|
||||||
|
* @see #removeCompletion(Completion)
|
||||||
|
*/
|
||||||
|
public void clear() {
|
||||||
|
completions.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a list of <tt>Completion</tt>s in this provider with the
|
||||||
|
* specified input text.
|
||||||
|
*
|
||||||
|
* @param inputText The input text to search for.
|
||||||
|
* @return A list of {@link Completion}s, or <code>null</code> if there
|
||||||
|
* are no matching <tt>Completion</tt>s.
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public List<Completion> getCompletionByInputText(String inputText) {
|
||||||
|
|
||||||
|
// Find any entry that matches this input text (there may be > 1).
|
||||||
|
int end = Collections.binarySearch(completions, inputText, comparator);
|
||||||
|
if (end<0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// There might be multiple entries with the same input text.
|
||||||
|
int start = end;
|
||||||
|
while (start>0 &&
|
||||||
|
comparator.compare(completions.get(start-1), inputText)==0) {
|
||||||
|
start--;
|
||||||
|
}
|
||||||
|
int count = completions.size();
|
||||||
|
while (++end<count &&
|
||||||
|
comparator.compare(completions.get(end), inputText)==0);
|
||||||
|
|
||||||
|
return completions.subList(start, end); // (inclusive, exclusive)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
protected List<Completion> getCompletionsImpl(JTextComponent comp) {
|
||||||
|
|
||||||
|
List<Completion> retVal = new ArrayList<Completion>();
|
||||||
|
String text = getAlreadyEnteredText(comp);
|
||||||
|
|
||||||
|
if (text!=null) {
|
||||||
|
|
||||||
|
int index = Collections.binarySearch(completions, text, comparator);
|
||||||
|
if (index<0) { // No exact match
|
||||||
|
index = -index - 1;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// If there are several overloads for the function being
|
||||||
|
// completed, Collections.binarySearch() will return the index
|
||||||
|
// of one of those overloads, but we must return all of them,
|
||||||
|
// so search backward until we find the first one.
|
||||||
|
int pos = index - 1;
|
||||||
|
while (pos>0 &&
|
||||||
|
comparator.compare(completions.get(pos), text)==0) {
|
||||||
|
retVal.add(completions.get(pos));
|
||||||
|
pos--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
while (index<completions.size()) {
|
||||||
|
Completion c = completions.get(index);
|
||||||
|
if (Util.startsWithIgnoreCase(c.getInputText(), text)) {
|
||||||
|
retVal.add(c);
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return retVal;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes the specified completion from this provider. This method
|
||||||
|
* will not remove completions from the parent provider, if there is one.
|
||||||
|
*
|
||||||
|
* @param c The completion to remove.
|
||||||
|
* @return <code>true</code> if this provider contained the specified
|
||||||
|
* completion.
|
||||||
|
* @see #clear()
|
||||||
|
* @see #addCompletion(Completion)
|
||||||
|
* @see #addCompletions(List)
|
||||||
|
*/
|
||||||
|
public boolean removeCompletion(Completion c) {
|
||||||
|
// Don't just call completions.remove(c) as it'll be a linear search.
|
||||||
|
int index = Collections.binarySearch(completions, c);
|
||||||
|
if (index<0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
completions.remove(index);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A comparator that compares the input text of a {@link Completion}
|
||||||
|
* against a String lexicographically, ignoring case.
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("rawtypes")
|
||||||
|
public static class CaseInsensitiveComparator implements Comparator,
|
||||||
|
Serializable {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int compare(Object o1, Object o2) {
|
||||||
|
String s1 = o1 instanceof String ? (String)o1 :
|
||||||
|
((Completion)o1).getInputText();
|
||||||
|
String s2 = o2 instanceof String ? (String)o2 :
|
||||||
|
((Completion)o2).getInputText();
|
||||||
|
return String.CASE_INSENSITIVE_ORDER.compare(s1, s2);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,597 @@
|
|||||||
|
/*
|
||||||
|
* 12/21/2008
|
||||||
|
*
|
||||||
|
* AutoCompleteDescWindow.java - A window containing a description of the
|
||||||
|
* currently selected completion.
|
||||||
|
*
|
||||||
|
* This library is distributed under a modified BSD license. See the included
|
||||||
|
* AutoComplete.License.txt file for details.
|
||||||
|
*/
|
||||||
|
package org.fife.ui.autocomplete;
|
||||||
|
|
||||||
|
import java.awt.BorderLayout;
|
||||||
|
import java.awt.Component;
|
||||||
|
import java.awt.ComponentOrientation;
|
||||||
|
import java.awt.Graphics;
|
||||||
|
import java.awt.Insets;
|
||||||
|
import java.awt.Window;
|
||||||
|
import java.awt.event.ActionEvent;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.net.URISyntaxException;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.ResourceBundle;
|
||||||
|
import javax.swing.AbstractAction;
|
||||||
|
import javax.swing.Action;
|
||||||
|
import javax.swing.BorderFactory;
|
||||||
|
import javax.swing.Icon;
|
||||||
|
import javax.swing.ImageIcon;
|
||||||
|
import javax.swing.JButton;
|
||||||
|
import javax.swing.JEditorPane;
|
||||||
|
import javax.swing.JPanel;
|
||||||
|
import javax.swing.JScrollPane;
|
||||||
|
import javax.swing.JToolBar;
|
||||||
|
import javax.swing.JWindow;
|
||||||
|
import javax.swing.SwingUtilities;
|
||||||
|
import javax.swing.Timer;
|
||||||
|
import javax.swing.UIManager;
|
||||||
|
import javax.swing.border.AbstractBorder;
|
||||||
|
import javax.swing.border.Border;
|
||||||
|
import javax.swing.event.HyperlinkEvent;
|
||||||
|
import javax.swing.event.HyperlinkListener;
|
||||||
|
|
||||||
|
import org.fife.ui.rsyntaxtextarea.PopupWindowDecorator;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The optional "description" window that describes the currently selected
|
||||||
|
* item in the auto-completion window.
|
||||||
|
*
|
||||||
|
* @author Robert Futrell
|
||||||
|
* @version 1.0
|
||||||
|
*/
|
||||||
|
class AutoCompleteDescWindow extends JWindow implements HyperlinkListener,
|
||||||
|
DescWindowCallback {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The parent AutoCompletion instance.
|
||||||
|
*/
|
||||||
|
private AutoCompletion ac;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Renders the HTML description.
|
||||||
|
*/
|
||||||
|
private JEditorPane descArea;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The scroll pane that {@link #descArea} is in.
|
||||||
|
*/
|
||||||
|
private JScrollPane scrollPane;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The bottom panel, containing the toolbar and size grip.
|
||||||
|
*/
|
||||||
|
private JPanel bottomPanel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The toolbar with "back" and "forward" buttons.
|
||||||
|
*/
|
||||||
|
private JToolBar descWindowNavBar;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Action that goes to the previous description displayed.
|
||||||
|
*/
|
||||||
|
private Action backAction;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Action that goes to the next description displayed.
|
||||||
|
*/
|
||||||
|
private Action forwardAction;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* History of descriptions displayed.
|
||||||
|
*/
|
||||||
|
private List<HistoryEntry> history;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The current position in {@link #history}.
|
||||||
|
*/
|
||||||
|
private int historyPos;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides a slight delay between asking to set a description and actually
|
||||||
|
* displaying it, so that if the user is scrolling quickly through
|
||||||
|
* completions, those with slow-to-calculate summaries won't bog down the
|
||||||
|
* scrolling.
|
||||||
|
*/
|
||||||
|
private Timer timer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The action that listens for the timer to fire.
|
||||||
|
*/
|
||||||
|
private TimerAction timerAction;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The resource bundle for this window.
|
||||||
|
*/
|
||||||
|
private ResourceBundle bundle;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The amount of time to wait after the user changes the selected
|
||||||
|
* completion to refresh the description. This delay is in place to help
|
||||||
|
* performance for {@link Completion}s that may be slow to compute their
|
||||||
|
* summary text.
|
||||||
|
*/
|
||||||
|
private static final int INITIAL_TIMER_DELAY = 120;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The resource bundle name.
|
||||||
|
*/
|
||||||
|
private static final String MSG =
|
||||||
|
"org.fife.ui.autocomplete.AutoCompleteDescWindow";
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
*
|
||||||
|
* @param owner The parent window.
|
||||||
|
* @param ac The parent auto-completion.
|
||||||
|
*/
|
||||||
|
public AutoCompleteDescWindow(Window owner, AutoCompletion ac) {
|
||||||
|
|
||||||
|
super(owner);
|
||||||
|
this.ac = ac;
|
||||||
|
|
||||||
|
ComponentOrientation o = ac.getTextComponentOrientation();
|
||||||
|
|
||||||
|
JPanel cp = new JPanel(new BorderLayout());
|
||||||
|
cp.setBorder(TipUtil.getToolTipBorder());
|
||||||
|
|
||||||
|
descArea = new JEditorPane("text/html", null);
|
||||||
|
TipUtil.tweakTipEditorPane(descArea);
|
||||||
|
descArea.addHyperlinkListener(this);
|
||||||
|
scrollPane = new JScrollPane(descArea);
|
||||||
|
Border b = BorderFactory.createEmptyBorder();
|
||||||
|
scrollPane.setBorder(b);
|
||||||
|
scrollPane.setViewportBorder(b);
|
||||||
|
scrollPane.setBackground(descArea.getBackground());
|
||||||
|
scrollPane.getViewport().setBackground(descArea.getBackground());
|
||||||
|
cp.add(scrollPane);
|
||||||
|
|
||||||
|
descWindowNavBar = new JToolBar();
|
||||||
|
backAction = new ToolBarBackAction(o.isLeftToRight());
|
||||||
|
forwardAction = new ToolBarForwardAction(o.isLeftToRight());
|
||||||
|
descWindowNavBar.setFloatable(false);
|
||||||
|
descWindowNavBar.add(new JButton(backAction));
|
||||||
|
descWindowNavBar.add(new JButton(forwardAction));
|
||||||
|
|
||||||
|
bottomPanel = new JPanel(new BorderLayout());
|
||||||
|
b = new AbstractBorder() {
|
||||||
|
@Override
|
||||||
|
public Insets getBorderInsets(Component c) {
|
||||||
|
return new Insets(1, 0, 0, 0);
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public void paintBorder(Component c, Graphics g, int x, int y, int w, int h) {
|
||||||
|
g.setColor(UIManager.getColor("controlDkShadow"));
|
||||||
|
g.drawLine(x,y, x+w-1,y);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
bottomPanel.setBorder(b);
|
||||||
|
SizeGrip rp = new SizeGrip();
|
||||||
|
bottomPanel.add(descWindowNavBar, BorderLayout.LINE_START);
|
||||||
|
bottomPanel.add(rp, BorderLayout.LINE_END);
|
||||||
|
cp.add(bottomPanel, BorderLayout.SOUTH);
|
||||||
|
setContentPane(cp);
|
||||||
|
|
||||||
|
applyComponentOrientation(o);
|
||||||
|
setFocusableWindowState(false);
|
||||||
|
|
||||||
|
// Give apps a chance to decorate us with drop shadows, etc.
|
||||||
|
if (Util.getShouldAllowDecoratingMainAutoCompleteWindows()) {
|
||||||
|
PopupWindowDecorator decorator = PopupWindowDecorator.get();
|
||||||
|
if (decorator!=null) {
|
||||||
|
decorator.decorate(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
history = new ArrayList<HistoryEntry>(1); // Usually small
|
||||||
|
historyPos = -1;
|
||||||
|
|
||||||
|
timerAction = new TimerAction();
|
||||||
|
timer = new Timer(INITIAL_TIMER_DELAY, timerAction);
|
||||||
|
timer.setRepeats(false);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the currently displayed description and updates the history.
|
||||||
|
*
|
||||||
|
* @param historyItem The item to add to the history.
|
||||||
|
*/
|
||||||
|
private void addToHistory(HistoryEntry historyItem) {
|
||||||
|
history.add(++historyPos, historyItem);
|
||||||
|
clearHistoryAfterCurrentPos();
|
||||||
|
setActionStates();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clears the history of viewed descriptions.
|
||||||
|
*/
|
||||||
|
private void clearHistory() {
|
||||||
|
history.clear(); // Try to free some memory.
|
||||||
|
historyPos = -1;
|
||||||
|
if (descWindowNavBar!=null) {
|
||||||
|
setActionStates();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Makes the current history page the last one in the history.
|
||||||
|
*/
|
||||||
|
private void clearHistoryAfterCurrentPos() {
|
||||||
|
for (int i=history.size()-1; i>historyPos; i--) {
|
||||||
|
history.remove(i);
|
||||||
|
}
|
||||||
|
setActionStates();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copies from the description text area, if it is visible and there is
|
||||||
|
* a selection.
|
||||||
|
*
|
||||||
|
* @return Whether a copy occurred.
|
||||||
|
*/
|
||||||
|
public boolean copy() {
|
||||||
|
if (isVisible() &&
|
||||||
|
descArea.getSelectionStart()!=descArea.getSelectionEnd()) {
|
||||||
|
descArea.copy();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the localized message for the specified key.
|
||||||
|
*
|
||||||
|
* @param key The key.
|
||||||
|
* @return The localized message.
|
||||||
|
*/
|
||||||
|
private String getString(String key) {
|
||||||
|
if (bundle==null) {
|
||||||
|
bundle = ResourceBundle.getBundle(MSG);
|
||||||
|
}
|
||||||
|
return bundle.getString(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when a hyperlink is clicked.
|
||||||
|
*
|
||||||
|
* @param e The event.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void hyperlinkUpdate(HyperlinkEvent e) {
|
||||||
|
|
||||||
|
HyperlinkEvent.EventType type = e.getEventType();
|
||||||
|
if (!type.equals(HyperlinkEvent.EventType.ACTIVATED)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Users can redirect URL's, perhaps to a local copy of documentation.
|
||||||
|
URL url = e.getURL();
|
||||||
|
if (url!=null) {
|
||||||
|
LinkRedirector redirector = AutoCompletion.getLinkRedirector();
|
||||||
|
if (redirector!=null) {
|
||||||
|
URL newUrl = redirector.possiblyRedirect(url);
|
||||||
|
if (newUrl!=null && newUrl!=url) {
|
||||||
|
url = newUrl;
|
||||||
|
e = new HyperlinkEvent(e.getSource(), e.getEventType(),
|
||||||
|
newUrl, e.getDescription(), e.getSourceElement());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Custom hyperlink handler for this completion type
|
||||||
|
ExternalURLHandler handler = ac.getExternalURLHandler();
|
||||||
|
if (handler!=null) {
|
||||||
|
HistoryEntry current = history.get(historyPos);
|
||||||
|
handler.urlClicked(e, current.completion, this);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// No custom handler...
|
||||||
|
if (url!=null) {
|
||||||
|
// Try loading in external browser (Java 6+ only).
|
||||||
|
try {
|
||||||
|
Util.browse(new URI(url.toString()));
|
||||||
|
} catch (/*IO*/URISyntaxException ioe) {
|
||||||
|
UIManager.getLookAndFeel().provideErrorFeedback(descArea);
|
||||||
|
ioe.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else { // Assume simple function name text, like in c.xml
|
||||||
|
// FIXME: This is really a hack, and we assume we can find the
|
||||||
|
// linked-to item in the same CompletionProvider.
|
||||||
|
AutoCompletePopupWindow parent =
|
||||||
|
(AutoCompletePopupWindow)getParent();
|
||||||
|
CompletionProvider p = parent.getSelection().getProvider();
|
||||||
|
if (p instanceof AbstractCompletionProvider) {
|
||||||
|
String name = e.getDescription();
|
||||||
|
List<Completion> l = ((AbstractCompletionProvider)p).
|
||||||
|
getCompletionByInputText(name);
|
||||||
|
if (l!=null && !l.isEmpty()) {
|
||||||
|
// Just use the 1st one if there's more than 1
|
||||||
|
Completion c = l.get(0);
|
||||||
|
setDescriptionFor(c, true);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
UIManager.getLookAndFeel().provideErrorFeedback(descArea);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enables or disables the back and forward actions as appropriate.
|
||||||
|
*/
|
||||||
|
private void setActionStates() {
|
||||||
|
// TODO: Localize this text!
|
||||||
|
String desc = null;
|
||||||
|
if (historyPos>0) {
|
||||||
|
backAction.setEnabled(true);
|
||||||
|
desc = "Back to " + history.get(historyPos-1);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
backAction.setEnabled(false);
|
||||||
|
}
|
||||||
|
backAction.putValue(Action.SHORT_DESCRIPTION, desc);
|
||||||
|
if (historyPos>-1 && historyPos<history.size()-1) {
|
||||||
|
forwardAction.setEnabled(true);
|
||||||
|
desc = "Forward to " + history.get(historyPos+1);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
forwardAction.setEnabled(false);
|
||||||
|
desc = null;
|
||||||
|
}
|
||||||
|
forwardAction.putValue(Action.SHORT_DESCRIPTION, desc);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the description displayed in this window. This clears the
|
||||||
|
* history.
|
||||||
|
*
|
||||||
|
* @param item The item whose description you want to display.
|
||||||
|
*/
|
||||||
|
public void setDescriptionFor(Completion item) {
|
||||||
|
setDescriptionFor(item, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the description displayed in this window.
|
||||||
|
*
|
||||||
|
* @param item The item whose description you want to display.
|
||||||
|
* @param addToHistory Whether to add this page to the page history
|
||||||
|
* (as opposed to clearing it and starting anew).
|
||||||
|
*/
|
||||||
|
protected void setDescriptionFor(Completion item, boolean addToHistory) {
|
||||||
|
setDescriptionFor(item, null, addToHistory);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the description displayed in this window.
|
||||||
|
*
|
||||||
|
* @param item The item whose description you want to display.
|
||||||
|
* @parma anchor The anchor to jump to, or <code>null</code> if none.
|
||||||
|
* @param addToHistory Whether to add this page to the page history
|
||||||
|
* (as opposed to clearing it and starting anew).
|
||||||
|
*/
|
||||||
|
protected void setDescriptionFor(Completion item, String anchor,
|
||||||
|
boolean addToHistory) {
|
||||||
|
timer.stop();
|
||||||
|
timerAction.setCompletion(item, anchor, addToHistory);
|
||||||
|
timer.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void setDisplayedDesc(Completion completion, final String anchor,
|
||||||
|
boolean addToHistory) {
|
||||||
|
|
||||||
|
String desc = completion==null ? null : completion.getSummary();
|
||||||
|
if (desc==null) {
|
||||||
|
desc = "<html><em>" + getString("NoDescAvailable") + "</em>";
|
||||||
|
}
|
||||||
|
descArea.setText(desc);
|
||||||
|
if (anchor!=null) {
|
||||||
|
SwingUtilities.invokeLater(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
descArea.scrollToReference(anchor);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
descArea.setCaretPosition(0); // In case of scrolling
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!addToHistory) {
|
||||||
|
// Remove everything first if this is going to be the only
|
||||||
|
// thing in history.
|
||||||
|
clearHistory();
|
||||||
|
}
|
||||||
|
addToHistory(new HistoryEntry(completion, desc, null));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void setVisible(boolean visible) {
|
||||||
|
if (!visible) {
|
||||||
|
clearHistory();
|
||||||
|
}
|
||||||
|
super.setVisible(visible);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback for custom <code>ExternalURLHandler</code>s.
|
||||||
|
*
|
||||||
|
* @param completion The completion to display.
|
||||||
|
* @param anchor The anchor in the HTML to jump to, or <code>null</code>
|
||||||
|
* if none.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void showSummaryFor(Completion completion, String anchor) {
|
||||||
|
setDescriptionFor(completion, anchor, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called by the parent completion popup window the LookAndFeel is updated.
|
||||||
|
*/
|
||||||
|
public void updateUI() {
|
||||||
|
SwingUtilities.updateComponentTreeUI(this);
|
||||||
|
// Update editor pane for new font, bg, selection colors, etc.
|
||||||
|
TipUtil.tweakTipEditorPane(descArea);
|
||||||
|
scrollPane.setBackground(descArea.getBackground());
|
||||||
|
scrollPane.getViewport().setBackground(descArea.getBackground());
|
||||||
|
((JPanel)getContentPane()).setBorder(TipUtil.getToolTipBorder());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A completion and its cached summary text.
|
||||||
|
*/
|
||||||
|
private static class HistoryEntry {
|
||||||
|
|
||||||
|
public Completion completion;
|
||||||
|
public String summary;
|
||||||
|
public String anchor;
|
||||||
|
|
||||||
|
public HistoryEntry(Completion completion, String summary,
|
||||||
|
String anchor) {
|
||||||
|
this.completion = completion;
|
||||||
|
this.summary = summary;
|
||||||
|
this.anchor = anchor;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Overridden to display a short name for the completion, since it's
|
||||||
|
* used in the tool tips for the "back" and "forward" buttons.
|
||||||
|
*
|
||||||
|
* @return A string representation of this history entry.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return completion.getInputText();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Action that actually updates the summary text displayed.
|
||||||
|
*/
|
||||||
|
private class TimerAction extends AbstractAction {
|
||||||
|
|
||||||
|
private Completion completion;
|
||||||
|
private String anchor;
|
||||||
|
private boolean addToHistory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the timer is fired.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void actionPerformed(ActionEvent e) {
|
||||||
|
setDisplayedDesc(completion, anchor, addToHistory);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCompletion(Completion c, String anchor,
|
||||||
|
boolean addToHistory) {
|
||||||
|
this.completion = c;
|
||||||
|
this.anchor = anchor;
|
||||||
|
this.addToHistory = addToHistory;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Action that moves to the previous description displayed.
|
||||||
|
*/
|
||||||
|
class ToolBarBackAction extends AbstractAction {
|
||||||
|
|
||||||
|
public ToolBarBackAction(boolean ltr) {
|
||||||
|
String img = "org/fife/ui/autocomplete/arrow_" +
|
||||||
|
(ltr ? "left.png" : "right.png");
|
||||||
|
ClassLoader cl = getClass().getClassLoader();
|
||||||
|
Icon icon = new ImageIcon(cl.getResource(img));
|
||||||
|
putValue(Action.SMALL_ICON, icon);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void actionPerformed(ActionEvent e) {
|
||||||
|
if (historyPos>0) {
|
||||||
|
HistoryEntry pair = history.get(--historyPos);
|
||||||
|
descArea.setText(pair.summary);
|
||||||
|
if (pair.anchor!=null) {
|
||||||
|
//System.out.println("Scrolling to: " + pair.anchor);
|
||||||
|
descArea.scrollToReference(pair.anchor);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
descArea.setCaretPosition(0);
|
||||||
|
}
|
||||||
|
setActionStates();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Action that moves to the previous description displayed.
|
||||||
|
*/
|
||||||
|
class ToolBarForwardAction extends AbstractAction {
|
||||||
|
|
||||||
|
public ToolBarForwardAction(boolean ltr) {
|
||||||
|
String img = "org/fife/ui/autocomplete/arrow_" +
|
||||||
|
(ltr ? "right.png" : "left.png");
|
||||||
|
ClassLoader cl = getClass().getClassLoader();
|
||||||
|
Icon icon = new ImageIcon(cl.getResource(img));
|
||||||
|
putValue(Action.SMALL_ICON, icon);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void actionPerformed(ActionEvent e) {
|
||||||
|
if (history!=null && historyPos<history.size()-1) {
|
||||||
|
HistoryEntry pair = history.get(++historyPos);
|
||||||
|
descArea.setText(pair.summary);
|
||||||
|
if (pair.anchor!=null) {
|
||||||
|
//System.out.println("Scrolling to: " + pair.anchor);
|
||||||
|
descArea.scrollToReference(pair.anchor);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
descArea.setCaretPosition(0);
|
||||||
|
}
|
||||||
|
setActionStates();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,71 @@
|
|||||||
|
/*
|
||||||
|
* 02/08/2014
|
||||||
|
*
|
||||||
|
* This library is distributed under a modified BSD license. See the included
|
||||||
|
* AutoComplete.License.txt file for details.
|
||||||
|
*/
|
||||||
|
package org.fife.ui.autocomplete;
|
||||||
|
|
||||||
|
import java.util.EventObject;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An event fired by an instance of {@link AutoCompletion}. This can be
|
||||||
|
* used by applications that wish to be notified of the auto-complete popup
|
||||||
|
* window showing and hiding.
|
||||||
|
*
|
||||||
|
* @author Robert Futrell
|
||||||
|
* @version 1.0
|
||||||
|
*/
|
||||||
|
public class AutoCompletionEvent extends EventObject {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The type of this event.
|
||||||
|
*/
|
||||||
|
private Type type;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
*
|
||||||
|
* @param source The <code>AutoCompletion</code> instance that fired
|
||||||
|
* this event.
|
||||||
|
* @param type The event type.
|
||||||
|
*/
|
||||||
|
public AutoCompletionEvent(AutoCompletion source, Type type) {
|
||||||
|
super(source);
|
||||||
|
this.type = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the source <code>AutoCompletion</code> instance. This is just
|
||||||
|
* shorthand for <code>return (AutoCompletion)getSource();</code>.
|
||||||
|
*
|
||||||
|
* @return The source <code>AutoCompletion</code> instance.
|
||||||
|
*/
|
||||||
|
public AutoCompletion getAutoCompletion() {
|
||||||
|
return (AutoCompletion)getSource();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the type of this event.
|
||||||
|
*
|
||||||
|
* @return The type of this event.
|
||||||
|
*/
|
||||||
|
public Type getEventType() {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enumeration of the various types of this event.
|
||||||
|
*/
|
||||||
|
public static enum Type {
|
||||||
|
POPUP_SHOWN,
|
||||||
|
POPUP_HIDDEN
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
/*
|
||||||
|
* 02/08/2014
|
||||||
|
*
|
||||||
|
* This library is distributed under a modified BSD license. See the included
|
||||||
|
* AutoComplete.License.txt file for details.
|
||||||
|
*/
|
||||||
|
package org.fife.ui.autocomplete;
|
||||||
|
|
||||||
|
import java.util.EventListener;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An interface that allows listening for interesting events from an
|
||||||
|
* {@link AutoCompletion}.
|
||||||
|
*
|
||||||
|
* @author Robert Futrell
|
||||||
|
* @version 1.0
|
||||||
|
*/
|
||||||
|
public interface AutoCompletionListener extends EventListener {
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback notified when a change to the <code>AutoCompletion</code>'s
|
||||||
|
* status occurs.
|
||||||
|
*
|
||||||
|
* @param e The event.
|
||||||
|
*/
|
||||||
|
void autoCompleteUpdate(AutoCompletionEvent e);
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,120 @@
|
|||||||
|
/*
|
||||||
|
* 06/24/2012
|
||||||
|
*
|
||||||
|
* AutoCompletionStyleContext.java - Manages styles related to auto-completion.
|
||||||
|
*
|
||||||
|
* This library is distributed under a modified BSD license. See the included
|
||||||
|
* AutoComplete.License.txt file for details.
|
||||||
|
*/
|
||||||
|
package org.fife.ui.autocomplete;
|
||||||
|
|
||||||
|
import java.awt.Color;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Manages the colors shared across the library.
|
||||||
|
*
|
||||||
|
* @author Robert Futrell
|
||||||
|
* @version 1.0
|
||||||
|
*/
|
||||||
|
public class AutoCompletionStyleContext {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The color used to denote the ending caret position for parameterized
|
||||||
|
* completions.
|
||||||
|
*/
|
||||||
|
private Color parameterizedCompletionCursorPositionColor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The color used to highlight copies of editable parameters in
|
||||||
|
* parameterized completions.
|
||||||
|
*/
|
||||||
|
private Color parameterCopyColor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The color of the outline highlight used to denote editable parameters
|
||||||
|
* in parameterized completions.
|
||||||
|
*/
|
||||||
|
private Color parameterOutlineColor;
|
||||||
|
|
||||||
|
|
||||||
|
public AutoCompletionStyleContext() {
|
||||||
|
setParameterOutlineColor(Color.gray);
|
||||||
|
setParameterCopyColor(new Color(0xb4d7ff));
|
||||||
|
setParameterizedCompletionCursorPositionColor(new Color(0x00b400));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the color of the highlight painted on copies of editable
|
||||||
|
* parameters in parameterized completions.
|
||||||
|
*
|
||||||
|
* @return The color used.
|
||||||
|
* @see #setParameterCopyColor(Color)
|
||||||
|
*/
|
||||||
|
public Color getParameterCopyColor() {
|
||||||
|
return parameterCopyColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the color used to denote the ending caret position for
|
||||||
|
* parameterized completions.
|
||||||
|
*
|
||||||
|
* @return The color used.
|
||||||
|
* @see #setParameterizedCompletionCursorPositionColor(Color)
|
||||||
|
*/
|
||||||
|
public Color getParameterizedCompletionCursorPositionColor() {
|
||||||
|
return parameterizedCompletionCursorPositionColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the color of the outline highlight used to denote editable
|
||||||
|
* parameters in parameterized completions.
|
||||||
|
*
|
||||||
|
* @return The color used.
|
||||||
|
* @see #setParameterOutlineColor(Color)
|
||||||
|
*/
|
||||||
|
public Color getParameterOutlineColor() {
|
||||||
|
return parameterOutlineColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the color of the highlight painted on copies of editable
|
||||||
|
* parameters in parameterized completions.
|
||||||
|
*
|
||||||
|
* @param color The color to use.
|
||||||
|
* @see #setParameterCopyColor(Color)
|
||||||
|
*/
|
||||||
|
public void setParameterCopyColor(Color color) {
|
||||||
|
this.parameterCopyColor = color;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the color used to denote the ending caret position for
|
||||||
|
* parameterized completions.
|
||||||
|
*
|
||||||
|
* @param color The color to use.
|
||||||
|
* @see #getParameterizedCompletionCursorPositionColor()
|
||||||
|
*/
|
||||||
|
public void setParameterizedCompletionCursorPositionColor(Color color) {
|
||||||
|
this.parameterizedCompletionCursorPositionColor = color;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the color of the outline highlight used to denote editable
|
||||||
|
* parameters in parameterized completions.
|
||||||
|
*
|
||||||
|
* @param color The color to use.
|
||||||
|
* @see #getParameterOutlineColor()
|
||||||
|
*/
|
||||||
|
public void setParameterOutlineColor(Color color) {
|
||||||
|
this.parameterOutlineColor = color;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,146 @@
|
|||||||
|
/*
|
||||||
|
* 01/03/2009
|
||||||
|
*
|
||||||
|
* BasicCompletion.java - A straightforward Completion implementation.
|
||||||
|
*
|
||||||
|
* This library is distributed under a modified BSD license. See the included
|
||||||
|
* AutoComplete.License.txt file for details.
|
||||||
|
*/
|
||||||
|
package org.fife.ui.autocomplete;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A straightforward {@link Completion} implementation. This implementation
|
||||||
|
* can be used if you have a relatively short number of static completions
|
||||||
|
* with no (or short) summaries.<p>
|
||||||
|
*
|
||||||
|
* This implementation uses the replacement text as the input text. It also
|
||||||
|
* includes a "short description" field, which (if non-<code>null</code>), is
|
||||||
|
* used in the completion choices list.
|
||||||
|
*
|
||||||
|
* @author Robert Futrell
|
||||||
|
* @version 1.0
|
||||||
|
*/
|
||||||
|
public class BasicCompletion extends AbstractCompletion {
|
||||||
|
|
||||||
|
private String replacementText;
|
||||||
|
private String shortDesc;
|
||||||
|
private String summary;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
*
|
||||||
|
* @param provider The parent completion provider.
|
||||||
|
* @param replacementText The text to replace.
|
||||||
|
*/
|
||||||
|
public BasicCompletion(CompletionProvider provider, String replacementText){
|
||||||
|
this(provider, replacementText, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
*
|
||||||
|
* @param provider The parent completion provider.
|
||||||
|
* @param replacementText The text to replace.
|
||||||
|
* @param shortDesc A short description of the completion. This will be
|
||||||
|
* displayed in the completion list. This may be <code>null</code>.
|
||||||
|
*/
|
||||||
|
public BasicCompletion(CompletionProvider provider, String replacementText,
|
||||||
|
String shortDesc) {
|
||||||
|
this(provider, replacementText, shortDesc, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
*
|
||||||
|
* @param provider The parent completion provider.
|
||||||
|
* @param replacementText The text to replace.
|
||||||
|
* @param shortDesc A short description of the completion. This will be
|
||||||
|
* displayed in the completion list. This may be <code>null</code>.
|
||||||
|
* @param summary The summary of this completion. This should be HTML.
|
||||||
|
* This may be <code>null</code>.
|
||||||
|
*/
|
||||||
|
public BasicCompletion(CompletionProvider provider, String replacementText,
|
||||||
|
String shortDesc, String summary) {
|
||||||
|
super(provider);
|
||||||
|
this.replacementText = replacementText;
|
||||||
|
this.shortDesc = shortDesc;
|
||||||
|
this.summary = summary;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String getReplacementText() {
|
||||||
|
return replacementText;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the short description of this completion, usually used in
|
||||||
|
* the completion choices list.
|
||||||
|
*
|
||||||
|
* @return The short description, or <code>null</code> if there is none.
|
||||||
|
* @see #setShortDescription(String)
|
||||||
|
*/
|
||||||
|
public String getShortDescription() {
|
||||||
|
return shortDesc;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String getSummary() {
|
||||||
|
return summary;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the short description of this completion.
|
||||||
|
*
|
||||||
|
* @param shortDesc The short description of this completion.
|
||||||
|
* @see #getShortDescription()
|
||||||
|
*/
|
||||||
|
public void setShortDescription(String shortDesc) {
|
||||||
|
this.shortDesc = shortDesc;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the summary for this completion.
|
||||||
|
*
|
||||||
|
* @param summary The summary for this completion.
|
||||||
|
* @see #getSummary()
|
||||||
|
*/
|
||||||
|
public void setSummary(String summary) {
|
||||||
|
this.summary = summary;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a string representation of this completion. If the short
|
||||||
|
* description is not <code>null</code>, this method will return:
|
||||||
|
*
|
||||||
|
* <code>getInputText() + " - " + shortDesc</code>
|
||||||
|
*
|
||||||
|
* otherwise, it will return <tt>getInputText()</tt>.
|
||||||
|
*
|
||||||
|
* @return A string representation of this completion.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
if (shortDesc==null) {
|
||||||
|
return getInputText();
|
||||||
|
}
|
||||||
|
return getInputText() + " - " + shortDesc;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,154 @@
|
|||||||
|
/*
|
||||||
|
* 12/21/2008
|
||||||
|
*
|
||||||
|
* Completion.java - Represents a single completion choice.
|
||||||
|
*
|
||||||
|
* This library is distributed under a modified BSD license. See the included
|
||||||
|
* AutoComplete.License.txt file for details.
|
||||||
|
*/
|
||||||
|
package org.fife.ui.autocomplete;
|
||||||
|
|
||||||
|
import javax.swing.Icon;
|
||||||
|
import javax.swing.text.JTextComponent;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a completion choice. A {@link CompletionProvider} returns lists
|
||||||
|
* of objects implementing this interface. A <tt>Completion</tt> contains the
|
||||||
|
* following information:
|
||||||
|
*
|
||||||
|
* <ul>
|
||||||
|
* <li>The text the user must (begin to) input for this to be a completion
|
||||||
|
* choice.
|
||||||
|
* <li>The text that will be filled in if the user chooses this completion.
|
||||||
|
* Note that often, this is the same as the text the user must (begin to)
|
||||||
|
* enter, but this doesn't have to be the case.
|
||||||
|
* <li>Summary HTML that describes this completion. This is information that
|
||||||
|
* can be displayed in a helper "tooltip"-style window beside the
|
||||||
|
* completion list. This may be <code>null</code>. It may also be
|
||||||
|
* lazily generated to cut down on memory usage.
|
||||||
|
* <li>The <tt>CompletionProvider</tt> that returned this completion.
|
||||||
|
* <li>Tool tip text that can be displayed when a mouse hovers over this
|
||||||
|
* completion in a text component.
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* @author Robert Futrell
|
||||||
|
* @version 1.0
|
||||||
|
* @see AbstractCompletion
|
||||||
|
*/
|
||||||
|
public interface Completion extends Comparable<Completion> {
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compares this completion to another one lexicographically, ignoring
|
||||||
|
* case.
|
||||||
|
*
|
||||||
|
* @param other Another completion instance.
|
||||||
|
* @return How this completion compares to the other one.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public int compareTo(Completion other);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the portion of this completion that has already been entered
|
||||||
|
* into the text component. The match is case-insensitive.<p>
|
||||||
|
*
|
||||||
|
* This is a convenience method for:
|
||||||
|
* <code>getProvider().getAlreadyEnteredText(comp)</code>.
|
||||||
|
*
|
||||||
|
* @param comp The text component.
|
||||||
|
* @return The already-entered portion of this completion.
|
||||||
|
*/
|
||||||
|
public String getAlreadyEntered(JTextComponent comp);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the icon to use for this completion.
|
||||||
|
*
|
||||||
|
* @return The icon, or <code>null</code> for none.
|
||||||
|
*/
|
||||||
|
public Icon getIcon();
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the text that the user has to (start) typing for this completion
|
||||||
|
* to be offered. Note that this will usually be the same value as
|
||||||
|
* {@link #getReplacementText()}, but not always (a completion could be
|
||||||
|
* a way to implement shorthand, for example, "<code>sysout</code>" mapping
|
||||||
|
* to "<code>System.out.println(</code>").
|
||||||
|
*
|
||||||
|
* @return The text the user has to (start) typing for this completion to
|
||||||
|
* be offered.
|
||||||
|
* @see #getReplacementText()
|
||||||
|
*/
|
||||||
|
public String getInputText();
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the provider that returned this completion.
|
||||||
|
*
|
||||||
|
* @return The provider.
|
||||||
|
*/
|
||||||
|
public CompletionProvider getProvider();
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the "relevance" of this completion. This is used when sorting
|
||||||
|
* completions by their relevance. It is an abstract concept that may
|
||||||
|
* mean different things to different languages, and may depend on the
|
||||||
|
* context of the completion.<p>
|
||||||
|
*
|
||||||
|
* By default, all completions have a relevance of <code>0</code>. The
|
||||||
|
* higher the value returned by this method, the higher up in the list
|
||||||
|
* this completion will be; the lower the value returned, the lower it will
|
||||||
|
* be. <code>Completion</code>s with equal relevance values will be
|
||||||
|
* sorted alphabetically.
|
||||||
|
*
|
||||||
|
* @return The relevance of this completion.
|
||||||
|
*/
|
||||||
|
public int getRelevance();
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the text to insert as the result of this auto-completion. This
|
||||||
|
* is the "complete" text, including any text that replaces what the user
|
||||||
|
* has already typed.
|
||||||
|
*
|
||||||
|
* @return The replacement text.
|
||||||
|
* @see #getInputText()
|
||||||
|
*/
|
||||||
|
public String getReplacementText();
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the description of this auto-complete choice. This can be
|
||||||
|
* used in a popup "description window."
|
||||||
|
*
|
||||||
|
* @return This item's description. This should be HTML. It may be
|
||||||
|
* <code>null</code> if there is no description for this
|
||||||
|
* completion.
|
||||||
|
*/
|
||||||
|
public String getSummary();
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the tool tip text to display for mouse hovers over this
|
||||||
|
* completion.<p>
|
||||||
|
*
|
||||||
|
* Note that for this functionality to be enabled, a
|
||||||
|
* <tt>JTextComponent</tt> must be registered with the
|
||||||
|
* <tt>ToolTipManager</tt>, and the text component must know to search
|
||||||
|
* for this value. In the case of an
|
||||||
|
* <a href="http://fifesoft.com/rsyntaxtextarea">RSyntaxTextArea</a>, this
|
||||||
|
* can be done with a <tt>org.fife.ui.rtextarea.ToolTipSupplier</tt> that
|
||||||
|
* calls into
|
||||||
|
* {@link CompletionProvider#getCompletionsAt(JTextComponent, java.awt.Point)}.
|
||||||
|
*
|
||||||
|
* @return The tool tip text for this completion, or <code>null</code> if
|
||||||
|
* none.
|
||||||
|
*/
|
||||||
|
public String getToolTipText();
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,697 @@
|
|||||||
|
/*
|
||||||
|
* 12/23/2008
|
||||||
|
*
|
||||||
|
* CompletionCellRenderer.java - Cell renderer that can render the standard
|
||||||
|
* completion types like Eclipse or NetBeans does.
|
||||||
|
*
|
||||||
|
* This library is distributed under a modified BSD license. See the included
|
||||||
|
* AutoComplete.License.txt file for details.
|
||||||
|
*/
|
||||||
|
package org.fife.ui.autocomplete;
|
||||||
|
|
||||||
|
import java.awt.Color;
|
||||||
|
import java.awt.Component;
|
||||||
|
import java.awt.Font;
|
||||||
|
import java.awt.Graphics;
|
||||||
|
import java.awt.Rectangle;
|
||||||
|
import java.io.File;
|
||||||
|
import java.net.MalformedURLException;
|
||||||
|
import java.net.URL;
|
||||||
|
|
||||||
|
import javax.swing.DefaultListCellRenderer;
|
||||||
|
import javax.swing.Icon;
|
||||||
|
import javax.swing.ImageIcon;
|
||||||
|
import javax.swing.JList;
|
||||||
|
import javax.swing.SwingUtilities;
|
||||||
|
import javax.swing.plaf.basic.BasicHTML;
|
||||||
|
import javax.swing.text.View;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A cell renderer that adds some pizazz when rendering the standard
|
||||||
|
* {@link Completion} types, like Eclipse and NetBeans do. Specifically,
|
||||||
|
* this renderer handles:
|
||||||
|
*
|
||||||
|
* <ul>
|
||||||
|
* <li>{@link FunctionCompletion}s</li>
|
||||||
|
* <li>{@link VariableCompletion}s</li>
|
||||||
|
* <li>{@link MarkupTagCompletion}s</li>
|
||||||
|
* <li>{@link ShorthandCompletion}s</li>
|
||||||
|
* <li>{@link TemplateCompletion}s</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* @author Robert Futrell
|
||||||
|
* @version 1.0
|
||||||
|
*/
|
||||||
|
public class CompletionCellRenderer extends DefaultListCellRenderer {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The alternating background color, or <code>null</code> if alternating
|
||||||
|
* row colors should not be used.
|
||||||
|
*/
|
||||||
|
private static Color altBG;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The font to use when rendering items, or <code>null</code> if the
|
||||||
|
* list's default font should be used.
|
||||||
|
*/
|
||||||
|
private Font font;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether to display the types of fields and return types of functions
|
||||||
|
* in the completion text.
|
||||||
|
*/
|
||||||
|
private boolean showTypes;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The color to use when rendering types in completion text.
|
||||||
|
*/
|
||||||
|
private String typeColor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* During rendering, whether the item being rendered is selected.
|
||||||
|
*/
|
||||||
|
private boolean selected;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* During rendering, this is the "real" background color of the item being
|
||||||
|
* rendered (i.e., what its background color is if it isn't selected).
|
||||||
|
*/
|
||||||
|
private Color realBG;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The color to use for function arguments.
|
||||||
|
*/
|
||||||
|
private String paramColor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An icon to use when no appropriate icon is found.
|
||||||
|
*/
|
||||||
|
private Icon emptyIcon;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used in rendering calculations.
|
||||||
|
*/
|
||||||
|
private Rectangle paintTextR;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An optional delegate renderer (primarily for Substance).
|
||||||
|
*/
|
||||||
|
private DefaultListCellRenderer delegate;
|
||||||
|
|
||||||
|
private static final String SUBSTANCE_RENDERER_CLASS_NAME =
|
||||||
|
"org.pushingpixels.substance.api.renderers.SubstanceDefaultListCellRenderer";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Keeps the HTML descriptions from "wrapping" in the list, which cuts off
|
||||||
|
* words.
|
||||||
|
*/
|
||||||
|
private static final String PREFIX = "<html><nobr>";
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
*/
|
||||||
|
public CompletionCellRenderer() {
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor. This is primarily a hook for Substance, or any other
|
||||||
|
* Look and Feel whose renderers look drastically different than standard
|
||||||
|
* <code>DefaultListCellRenderer</code>s. Everything except for the text
|
||||||
|
* rendering will be done by the delegate. In almost all scenarios, you
|
||||||
|
* will want to use the no-argument constructor instead of this one.
|
||||||
|
*
|
||||||
|
* @param delegate The delegate renderer.
|
||||||
|
* @see #delegateToSubstanceRenderer()
|
||||||
|
*/
|
||||||
|
public CompletionCellRenderer(DefaultListCellRenderer delegate) {
|
||||||
|
setDelegateRenderer(delegate);
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates the icon to use if no icon is found for a specific completion.
|
||||||
|
* The default implementation returns a 16x16 empty icon.
|
||||||
|
*
|
||||||
|
* @return The icon.
|
||||||
|
* @see #getEmptyIcon()
|
||||||
|
*/
|
||||||
|
protected Icon createEmptyIcon() {
|
||||||
|
return new EmptyIcon(16);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a decent "parameter" color based on the current default
|
||||||
|
* foreground color.
|
||||||
|
*
|
||||||
|
* @return The parameter color to use.
|
||||||
|
*/
|
||||||
|
private String createParamColor() {
|
||||||
|
return Util.isLightForeground(getForeground()) ?
|
||||||
|
Util.getHexString(Util.getHyperlinkForeground()): "#aa0077";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a decent "type" color based on the current default foreground
|
||||||
|
* color.
|
||||||
|
*
|
||||||
|
* @return The type color to use.
|
||||||
|
*/
|
||||||
|
private String createTypeColor() {
|
||||||
|
return "#808080";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attempts to delegate rendering to a Substance cell renderer. This
|
||||||
|
* should only be called if Substance is known to be on the classpath.
|
||||||
|
*
|
||||||
|
* @throws Exception If Substance is not on the classpath, or some other
|
||||||
|
* error occurs creating the Substance cell renderer.
|
||||||
|
* @see Util#getUseSubstanceRenderers()
|
||||||
|
* @see #setDelegateRenderer(DefaultListCellRenderer)
|
||||||
|
*/
|
||||||
|
public void delegateToSubstanceRenderer() throws Exception {
|
||||||
|
Class<?> clazz = Class.forName(SUBSTANCE_RENDERER_CLASS_NAME);
|
||||||
|
DefaultListCellRenderer delegate =
|
||||||
|
(DefaultListCellRenderer)clazz.newInstance();
|
||||||
|
setDelegateRenderer(delegate);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the background color to use on alternating lines.
|
||||||
|
*
|
||||||
|
* @return The alternate background color. If this is <code>null</code>,
|
||||||
|
* alternating colors are not used.
|
||||||
|
* @see #setAlternateBackground(Color)
|
||||||
|
*/
|
||||||
|
public static Color getAlternateBackground() {
|
||||||
|
return altBG;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the delegate renderer, or <code>null</code> if there is none.
|
||||||
|
*
|
||||||
|
* @return The delegate renderer.
|
||||||
|
* @see #setDelegateRenderer(DefaultListCellRenderer)
|
||||||
|
*/
|
||||||
|
public DefaultListCellRenderer getDelegateRenderer() {
|
||||||
|
return delegate;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the font used when rendering completions.
|
||||||
|
*
|
||||||
|
* @return The font. If this is <code>null</code>, then the default list
|
||||||
|
* font is used.
|
||||||
|
* @see #setDisplayFont(Font)
|
||||||
|
*/
|
||||||
|
public Font getDisplayFont() {
|
||||||
|
return font;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the icon to use if no icon is found for a specific completion.
|
||||||
|
* This icon is lazily created if necessary.
|
||||||
|
*
|
||||||
|
* @return The icon.
|
||||||
|
* @see #createEmptyIcon()
|
||||||
|
*/
|
||||||
|
protected Icon getEmptyIcon() {
|
||||||
|
if (emptyIcon==null) {
|
||||||
|
emptyIcon = createEmptyIcon();
|
||||||
|
}
|
||||||
|
return emptyIcon;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an icon.
|
||||||
|
*
|
||||||
|
* @param resource The icon to retrieve. This should either be a file,
|
||||||
|
* or a resource loadable by the current ClassLoader.
|
||||||
|
* @return The icon.
|
||||||
|
*/
|
||||||
|
protected Icon getIcon(String resource) {
|
||||||
|
URL url = getClass().getResource(resource);
|
||||||
|
if (url==null) {
|
||||||
|
File file = new File(resource);
|
||||||
|
try {
|
||||||
|
url = file.toURI().toURL();
|
||||||
|
} catch (MalformedURLException mue) {
|
||||||
|
mue.printStackTrace(); // Never happens
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return url!=null ? new ImageIcon(url) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the renderer.
|
||||||
|
*
|
||||||
|
* @param list The list of choices being rendered.
|
||||||
|
* @param value The {@link Completion} being rendered.
|
||||||
|
* @param index The index into <code>list</code> being rendered.
|
||||||
|
* @param selected Whether the item is selected.
|
||||||
|
* @param hasFocus Whether the item has focus.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Component getListCellRendererComponent(JList list, Object value,
|
||||||
|
int index, boolean selected, boolean hasFocus) {
|
||||||
|
|
||||||
|
super.getListCellRendererComponent(list,value,index,selected,hasFocus);
|
||||||
|
if (font!=null) {
|
||||||
|
setFont(font); // Overrides super's setFont(list.getFont()).
|
||||||
|
}
|
||||||
|
this.selected = selected;
|
||||||
|
this.realBG = altBG!=null && (index&1)==1 ? altBG : list.getBackground();
|
||||||
|
|
||||||
|
Completion c = (Completion)value;
|
||||||
|
setIcon(c.getIcon());
|
||||||
|
|
||||||
|
if (c instanceof FunctionCompletion) {
|
||||||
|
FunctionCompletion fc = (FunctionCompletion)value;
|
||||||
|
prepareForFunctionCompletion(list, fc, index, selected, hasFocus);
|
||||||
|
}
|
||||||
|
else if (c instanceof VariableCompletion) {
|
||||||
|
VariableCompletion vc = (VariableCompletion)value;
|
||||||
|
prepareForVariableCompletion(list, vc, index, selected, hasFocus);
|
||||||
|
}
|
||||||
|
else if (c instanceof TemplateCompletion) {
|
||||||
|
TemplateCompletion tc = (TemplateCompletion)value;
|
||||||
|
prepareForTemplateCompletion(list, tc, index, selected, hasFocus);
|
||||||
|
}
|
||||||
|
else if (c instanceof MarkupTagCompletion) {
|
||||||
|
MarkupTagCompletion mtc = (MarkupTagCompletion)value;
|
||||||
|
prepareForMarkupTagCompletion(list, mtc, index, selected, hasFocus);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
prepareForOtherCompletion(list, c, index, selected, hasFocus);
|
||||||
|
}
|
||||||
|
|
||||||
|
// A delegate renderer might do its own alternate row striping
|
||||||
|
// (Substance does).
|
||||||
|
if (delegate!=null) {
|
||||||
|
delegate.getListCellRendererComponent(list, getText(), index,
|
||||||
|
selected, hasFocus);
|
||||||
|
delegate.setFont(getFont());
|
||||||
|
delegate.setIcon(getIcon());
|
||||||
|
return delegate;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!selected && (index&1)==1 && altBG!=null) {
|
||||||
|
setBackground(altBG);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether the types of fields and return types of methods are
|
||||||
|
* shown in the completion text.
|
||||||
|
*
|
||||||
|
* @return Whether to show the types.
|
||||||
|
* @see #setShowTypes(boolean)
|
||||||
|
*/
|
||||||
|
public boolean getShowTypes() {
|
||||||
|
return showTypes;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void init() {
|
||||||
|
//setDisplayFont(new Font("Monospaced", Font.PLAIN, 12));
|
||||||
|
setShowTypes(true);
|
||||||
|
typeColor = createTypeColor();
|
||||||
|
paramColor = createParamColor();
|
||||||
|
paintTextR = new Rectangle();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void paintComponent(Graphics g) {
|
||||||
|
|
||||||
|
//super.paintComponent(g);
|
||||||
|
|
||||||
|
g.setColor(realBG);
|
||||||
|
int iconW = 0;
|
||||||
|
if (getIcon()!=null) {
|
||||||
|
iconW = getIcon().getIconWidth();
|
||||||
|
}
|
||||||
|
if (selected && iconW>0) { // The icon area is never in the "selection"
|
||||||
|
g.fillRect(0, 0, iconW, getHeight());
|
||||||
|
g.setColor(getBackground());
|
||||||
|
g.fillRect(iconW,0, getWidth()-iconW,getHeight());
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
g.setColor(getBackground());
|
||||||
|
g.fillRect(0, 0, getWidth(), getHeight());
|
||||||
|
}
|
||||||
|
if (getIcon()!=null) {
|
||||||
|
getIcon().paintIcon(this, g, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
String text = getText();
|
||||||
|
if (text != null) {
|
||||||
|
paintTextR.setBounds(iconW,0, getWidth()-iconW,getHeight());
|
||||||
|
paintTextR.x += 3; // Force a slight margin
|
||||||
|
int space = paintTextR.height - g.getFontMetrics().getHeight();
|
||||||
|
View v = (View)getClientProperty(BasicHTML.propertyKey);
|
||||||
|
if (v != null) {
|
||||||
|
// HTML rendering doesn't auto-center vertically, for some
|
||||||
|
// reason
|
||||||
|
paintTextR.y += space/2;
|
||||||
|
paintTextR.height -= space;
|
||||||
|
v.paint(g, paintTextR);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
int textX = paintTextR.x;
|
||||||
|
int textY = paintTextR.y;// + g.getFontMetrics().getAscent();
|
||||||
|
//System.out.println(g.getFontMetrics().getAscent());
|
||||||
|
g.drawString(text, textX, textY);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prepares this renderer to display a function completion.
|
||||||
|
*
|
||||||
|
* @param list The list of choices being rendered.
|
||||||
|
* @param fc The completion to render.
|
||||||
|
* @param index The index into <code>list</code> being rendered.
|
||||||
|
* @param selected Whether the item is selected.
|
||||||
|
* @param hasFocus Whether the item has focus.
|
||||||
|
*/
|
||||||
|
protected void prepareForFunctionCompletion(JList list,
|
||||||
|
FunctionCompletion fc, int index, boolean selected, boolean hasFocus) {
|
||||||
|
|
||||||
|
StringBuilder sb = new StringBuilder(PREFIX);
|
||||||
|
sb.append(fc.getName());
|
||||||
|
|
||||||
|
char paramListStart = fc.getProvider().getParameterListStart();
|
||||||
|
if (paramListStart!=0) { // 0 => no start char
|
||||||
|
sb.append(paramListStart);
|
||||||
|
}
|
||||||
|
|
||||||
|
int paramCount = fc.getParamCount();
|
||||||
|
for (int i=0; i<paramCount; i++) {
|
||||||
|
FunctionCompletion.Parameter param = fc.getParam(i);
|
||||||
|
String type = param.getType();
|
||||||
|
String name = param.getName();
|
||||||
|
if (type!=null) {
|
||||||
|
if (!selected) {
|
||||||
|
sb.append("<font color='").append(paramColor).append("'>");
|
||||||
|
}
|
||||||
|
sb.append(type);
|
||||||
|
if (!selected) {
|
||||||
|
sb.append("</font>");
|
||||||
|
}
|
||||||
|
if (name!=null) {
|
||||||
|
sb.append(' ');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (name!=null) {
|
||||||
|
sb.append(name);
|
||||||
|
}
|
||||||
|
if (i<paramCount-1) {
|
||||||
|
sb.append(fc.getProvider().getParameterListSeparator());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
char paramListEnd = fc.getProvider().getParameterListEnd();
|
||||||
|
if (paramListEnd!=0) { // 0 => No parameter list end char
|
||||||
|
sb.append(paramListEnd);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (getShowTypes() && fc.getType()!=null) {
|
||||||
|
sb.append(" : ");
|
||||||
|
if (!selected) {
|
||||||
|
sb.append("<font color='").append(typeColor).append("'>");
|
||||||
|
}
|
||||||
|
sb.append(fc.getType());
|
||||||
|
if (!selected) {
|
||||||
|
sb.append("</font>");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setText(sb.toString());
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prepares this renderer to display a markup tag completion.
|
||||||
|
*
|
||||||
|
* @param list The list of choices being rendered.
|
||||||
|
* @param mc The completion to render.
|
||||||
|
* @param index The index into <code>list</code> being rendered.
|
||||||
|
* @param selected Whether the item is selected.
|
||||||
|
* @param hasFocus Whether the item has focus.
|
||||||
|
*/
|
||||||
|
protected void prepareForMarkupTagCompletion(JList list,
|
||||||
|
MarkupTagCompletion mc, int index, boolean selected, boolean hasFocus) {
|
||||||
|
|
||||||
|
StringBuilder sb = new StringBuilder(PREFIX);
|
||||||
|
sb.append(mc.getName());
|
||||||
|
|
||||||
|
setText(sb.toString());
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prepares this renderer to display a completion not specifically handled
|
||||||
|
* elsewhere.
|
||||||
|
*
|
||||||
|
* @param list The list of choices being rendered.
|
||||||
|
* @param c The completion to render.
|
||||||
|
* @param index The index into <code>list</code> being rendered.
|
||||||
|
* @param selected Whether the item is selected.
|
||||||
|
* @param hasFocus Whether the item has focus.
|
||||||
|
*/
|
||||||
|
protected void prepareForOtherCompletion(JList list,
|
||||||
|
Completion c, int index, boolean selected, boolean hasFocus) {
|
||||||
|
|
||||||
|
StringBuilder sb = new StringBuilder(PREFIX);
|
||||||
|
sb.append(c.getInputText());
|
||||||
|
|
||||||
|
if (c instanceof BasicCompletion) {
|
||||||
|
String definition = ((BasicCompletion)c).getShortDescription();
|
||||||
|
if (definition!=null) {
|
||||||
|
sb.append(" - ");
|
||||||
|
if (!selected) {
|
||||||
|
sb.append("<font color='").append(typeColor).append("'>");
|
||||||
|
}
|
||||||
|
sb.append(definition);
|
||||||
|
if (!selected) {
|
||||||
|
sb.append("</font>");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setText(sb.toString());
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prepares this renderer to display a template completion.
|
||||||
|
*
|
||||||
|
* @param list The list of choices being rendered.
|
||||||
|
* @param tc The completion to render.
|
||||||
|
* @param index The index into <code>list</code> being rendered.
|
||||||
|
* @param selected Whether the item is selected.
|
||||||
|
* @param hasFocus Whether the item has focus.
|
||||||
|
*/
|
||||||
|
protected void prepareForTemplateCompletion(JList list,
|
||||||
|
TemplateCompletion tc, int index, boolean selected, boolean hasFocus) {
|
||||||
|
|
||||||
|
StringBuilder sb = new StringBuilder(PREFIX);
|
||||||
|
sb.append(tc.getInputText());
|
||||||
|
|
||||||
|
String definition = tc.getShortDescription();
|
||||||
|
if (definition!=null) {
|
||||||
|
sb.append(" - ");
|
||||||
|
if (!selected) {
|
||||||
|
sb.append("<font color='").append(typeColor).append("'>");
|
||||||
|
}
|
||||||
|
sb.append(definition);
|
||||||
|
if (!selected) {
|
||||||
|
sb.append("</font>");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setText(sb.toString());
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prepares this renderer to display a variable completion.
|
||||||
|
*
|
||||||
|
* @param list The list of choices being rendered.
|
||||||
|
* @param vc The completion to render.
|
||||||
|
* @param index The index into <code>list</code> being rendered.
|
||||||
|
* @param selected Whether the item is selected.
|
||||||
|
* @param hasFocus Whether the item has focus.
|
||||||
|
*/
|
||||||
|
protected void prepareForVariableCompletion(JList list,
|
||||||
|
VariableCompletion vc, int index, boolean selected, boolean hasFocus) {
|
||||||
|
|
||||||
|
StringBuilder sb = new StringBuilder(PREFIX);
|
||||||
|
sb.append(vc.getName());
|
||||||
|
|
||||||
|
if (getShowTypes() && vc.getType()!=null) {
|
||||||
|
sb.append(" : ");
|
||||||
|
if (!selected) {
|
||||||
|
sb.append("<font color='").append(typeColor).append("'>");
|
||||||
|
}
|
||||||
|
sb.append(vc.getType());
|
||||||
|
if (!selected) {
|
||||||
|
sb.append("</font>");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setText(sb.toString());
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the background color to use on alternating lines.
|
||||||
|
*
|
||||||
|
* @param altBG The new alternate background color. If this is
|
||||||
|
* <code>null</code>, alternating lines will not use different
|
||||||
|
* background colors.
|
||||||
|
* @see #getAlternateBackground()
|
||||||
|
*/
|
||||||
|
public static void setAlternateBackground(Color altBG) {
|
||||||
|
CompletionCellRenderer.altBG = altBG;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the delegate renderer. Most users will never use this method; it
|
||||||
|
* is primarily a hook for Substance and other Look and Feels whose
|
||||||
|
* renderers look drastically different from the standard
|
||||||
|
* <code>DefaultListCellRenderer</code>.
|
||||||
|
*
|
||||||
|
* @param delegate The new delegate renderer. If this is <code>null</code>,
|
||||||
|
* the default rendering of this component is used.
|
||||||
|
* @see #getDelegateRenderer()
|
||||||
|
* @see #delegateToSubstanceRenderer()
|
||||||
|
*/
|
||||||
|
public void setDelegateRenderer(DefaultListCellRenderer delegate) {
|
||||||
|
this.delegate = delegate;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the font to use when rendering completion items.
|
||||||
|
*
|
||||||
|
* @param font The font to use. If this is <code>null</code>, then
|
||||||
|
* the default list font is used.
|
||||||
|
* @see #getDisplayFont()
|
||||||
|
*/
|
||||||
|
public void setDisplayFont(Font font) {
|
||||||
|
this.font = font;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the icon to display based off of a completion, falling back to the
|
||||||
|
* empty icon if the completion has no icon.
|
||||||
|
*
|
||||||
|
* @param completion The completion to check.
|
||||||
|
* @see #setIconWithDefault(Completion, Icon)
|
||||||
|
*/
|
||||||
|
protected void setIconWithDefault(Completion completion) {
|
||||||
|
setIconWithDefault(completion, getEmptyIcon());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the icon to display based off of a completion, falling back to a
|
||||||
|
* default icon if the completion has no icon.
|
||||||
|
*
|
||||||
|
* @param completion The completion to check.
|
||||||
|
* @param defaultIcon The icon to use if <code>completion</code> does not
|
||||||
|
* specify an icon.
|
||||||
|
* @see #setIconWithDefault(Completion)
|
||||||
|
*/
|
||||||
|
protected void setIconWithDefault(Completion completion, Icon defaultIcon) {
|
||||||
|
Icon icon = completion.getIcon();
|
||||||
|
setIcon(icon!=null ? icon :
|
||||||
|
(defaultIcon!=null ? defaultIcon : emptyIcon));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the color to use for function arguments.
|
||||||
|
*
|
||||||
|
* @param color The color to use. This is ignored if <code>null</code>.
|
||||||
|
* @see #setTypeColor(Color)
|
||||||
|
*/
|
||||||
|
public void setParamColor(Color color) {
|
||||||
|
if (color!=null) {
|
||||||
|
paramColor = Util.getHexString(color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets whether the types of fields and return types of methods are
|
||||||
|
* shown in the completion text.
|
||||||
|
*
|
||||||
|
* @param show Whether to show the types.
|
||||||
|
* @see #getShowTypes()
|
||||||
|
*/
|
||||||
|
public void setShowTypes(boolean show) {
|
||||||
|
this.showTypes = show;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the color to use for function/field types. Note that if
|
||||||
|
* {@link #getShowTypes()} returns <code>false</code>, this property
|
||||||
|
* effectively does nothing.
|
||||||
|
*
|
||||||
|
* @param color The color to use for types. This is ignored if
|
||||||
|
* <code>null</code>.
|
||||||
|
* @see #setShowTypes(boolean)
|
||||||
|
* @see #setParamColor(Color)
|
||||||
|
*/
|
||||||
|
public void setTypeColor(Color color) {
|
||||||
|
if (color!=null) {
|
||||||
|
typeColor = Util.getHexString(color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Overridden to update our delegate, if necessary.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void updateUI() {
|
||||||
|
super.updateUI();
|
||||||
|
if (delegate!=null) {
|
||||||
|
SwingUtilities.updateComponentTreeUI(delegate);
|
||||||
|
}
|
||||||
|
paramColor = createParamColor();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,90 @@
|
|||||||
|
/*
|
||||||
|
* 12/22/2008
|
||||||
|
*
|
||||||
|
* CompletionListModel.java - A model that allows bulk addition of elements.
|
||||||
|
*
|
||||||
|
* This library is distributed under a modified BSD license. See the included
|
||||||
|
* AutoComplete.License.txt file for details.
|
||||||
|
*/
|
||||||
|
package org.fife.ui.autocomplete;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import javax.swing.AbstractListModel;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A list model implementation that allows the bulk addition of elements.
|
||||||
|
* This is the only feature missing from <code>DefaultListModel</code> that
|
||||||
|
* we need.
|
||||||
|
*
|
||||||
|
* @author Robert Futrell
|
||||||
|
* @version 1.0
|
||||||
|
*/
|
||||||
|
class CompletionListModel extends AbstractListModel {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Container for items in this model.
|
||||||
|
*/
|
||||||
|
private List<Completion> delegate;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
*/
|
||||||
|
public CompletionListModel() {
|
||||||
|
delegate = new ArrayList<Completion>();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes all of the elements from this list. The list will
|
||||||
|
* be empty after this call returns (unless it throws an exception).
|
||||||
|
*
|
||||||
|
* @see #setContents(Collection)
|
||||||
|
*/
|
||||||
|
public void clear() {
|
||||||
|
int end = delegate.size()-1;
|
||||||
|
delegate.clear();
|
||||||
|
if (end >= 0) {
|
||||||
|
fireIntervalRemoved(this, 0, end);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Object getElementAt(int index) {
|
||||||
|
return delegate.get(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public int getSize() {
|
||||||
|
return delegate.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the contents of this model. All previous contents are removed.
|
||||||
|
*
|
||||||
|
* @param contents The new contents of this model.
|
||||||
|
*/
|
||||||
|
public void setContents(Collection<Completion> contents) {
|
||||||
|
clear();
|
||||||
|
int count = contents.size();
|
||||||
|
if (count>0) {
|
||||||
|
delegate.addAll(contents);
|
||||||
|
fireIntervalAdded(this, 0, count-1); // endpoints included (!)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,213 @@
|
|||||||
|
/*
|
||||||
|
* 12/21/2008
|
||||||
|
*
|
||||||
|
* CompletionProvider.java - Provides autocompletion values based on the
|
||||||
|
* text currently in a text component.
|
||||||
|
*
|
||||||
|
* This library is distributed under a modified BSD license. See the included
|
||||||
|
* AutoComplete.License.txt file for details.
|
||||||
|
*/
|
||||||
|
package org.fife.ui.autocomplete;
|
||||||
|
|
||||||
|
import java.awt.Point;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import javax.swing.ListCellRenderer;
|
||||||
|
import javax.swing.text.JTextComponent;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides autocompletion values to an {@link AutoCompletion}.<p>
|
||||||
|
*
|
||||||
|
* Completion providers can have an optional parent. Parents are searched for
|
||||||
|
* completions when their children are. This allows for chaining of completion
|
||||||
|
* providers.
|
||||||
|
*
|
||||||
|
* @author Robert Futrell
|
||||||
|
* @version 1.0
|
||||||
|
*/
|
||||||
|
public interface CompletionProvider {
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clears the values used to identify and insert "parameterized completions"
|
||||||
|
* (e.g. functions or methods). After this method is called, functions and
|
||||||
|
* methods will not have their parameters auto-completed.
|
||||||
|
*
|
||||||
|
* @see #setParameterizedCompletionParams(char, String, char)
|
||||||
|
*/
|
||||||
|
public void clearParameterizedCompletionParams();
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the text just before the current caret position that could be
|
||||||
|
* the start of something auto-completable.
|
||||||
|
*
|
||||||
|
* @param comp The text component.
|
||||||
|
* @return The text. A return value of <code>null</code> means nothing
|
||||||
|
* should be auto-completed; a value of an empty string
|
||||||
|
* (<code>""</code>) means auto-completion should still be
|
||||||
|
* considered (i.e., all possible choices are valid).
|
||||||
|
*/
|
||||||
|
public String getAlreadyEnteredText(JTextComponent comp);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the possible completions for the text component at the current
|
||||||
|
* caret position.
|
||||||
|
*
|
||||||
|
* @param comp The text component.
|
||||||
|
* @return The list of {@link Completion}s. If no completions are
|
||||||
|
* available, this method should return an empty list.
|
||||||
|
*/
|
||||||
|
public List<Completion> getCompletions(JTextComponent comp);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the completions that have been entered at the specified visual
|
||||||
|
* location. This can be used for tool tips when the user hovers the
|
||||||
|
* mouse over completed text.
|
||||||
|
*
|
||||||
|
* @param comp The text component.
|
||||||
|
* @param p The position, usually from a <tt>MouseEvent</tt>.
|
||||||
|
* @return The completions, or an empty list if there are none.
|
||||||
|
*/
|
||||||
|
public List<Completion> getCompletionsAt(JTextComponent comp, Point p);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the cell renderer for completions returned from this provider.
|
||||||
|
*
|
||||||
|
* @return The cell renderer, or <code>null</code> if the default should
|
||||||
|
* be used.
|
||||||
|
* @see #setListCellRenderer(ListCellRenderer)
|
||||||
|
*/
|
||||||
|
public ListCellRenderer getListCellRenderer();
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an object that can return a list of completion choices for
|
||||||
|
* parameters. This is used when a user code-completes a parameterized
|
||||||
|
* completion, such as a function or method. For any parameter to the
|
||||||
|
* function/method, this object can return possible completions.
|
||||||
|
*
|
||||||
|
* @return The parameter choices provider, or <code>null</code> if
|
||||||
|
* none is installed.
|
||||||
|
*/
|
||||||
|
public ParameterChoicesProvider getParameterChoicesProvider();
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a list of parameterized completions that have been entered
|
||||||
|
* at the current caret position of a text component (and thus can have
|
||||||
|
* their completion choices displayed).
|
||||||
|
*
|
||||||
|
* @param tc The text component.
|
||||||
|
* @return The list of {@link ParameterizedCompletion}s. If no completions
|
||||||
|
* are available, this may be <code>null</code>.
|
||||||
|
*/
|
||||||
|
public List<ParameterizedCompletion> getParameterizedCompletions(JTextComponent tc);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the text that marks the end of a list of parameters to a
|
||||||
|
* function or method.
|
||||||
|
*
|
||||||
|
* @return The text for a parameter list end, for example,
|
||||||
|
* '<code>)</code>'.
|
||||||
|
* @see #getParameterListStart()
|
||||||
|
* @see #getParameterListSeparator()
|
||||||
|
* @see #setParameterizedCompletionParams(char, String, char)
|
||||||
|
*/
|
||||||
|
public char getParameterListEnd();
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the text that separates parameters to a function or method.
|
||||||
|
*
|
||||||
|
* @return The text that separates parameters, for example,
|
||||||
|
* "<code>, </code>".
|
||||||
|
* @see #getParameterListStart()
|
||||||
|
* @see #getParameterListEnd()
|
||||||
|
* @see #setParameterizedCompletionParams(char, String, char)
|
||||||
|
*/
|
||||||
|
public String getParameterListSeparator();
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the text that marks the start of a list of parameters to a
|
||||||
|
* function or method.
|
||||||
|
*
|
||||||
|
* @return The text for a parameter list start, for example,
|
||||||
|
* "<code>(</code>".
|
||||||
|
* @see #getParameterListEnd()
|
||||||
|
* @see #getParameterListSeparator()
|
||||||
|
* @see #setParameterizedCompletionParams(char, String, char)
|
||||||
|
*/
|
||||||
|
public char getParameterListStart();
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the parent completion provider.
|
||||||
|
*
|
||||||
|
* @return The parent completion provider.
|
||||||
|
* @see #setParent(CompletionProvider)
|
||||||
|
*/
|
||||||
|
public CompletionProvider getParent();
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method is called if auto-activation is enabled in the parent
|
||||||
|
* {@link AutoCompletion} after the user types a single character. This
|
||||||
|
* provider should check the text at the current caret position of the
|
||||||
|
* text component, and decide whether auto-activation would be appropriate
|
||||||
|
* here. For example, a <code>CompletionProvider</code> for Java might
|
||||||
|
* want to return <code>true</code> for this method only if the last
|
||||||
|
* character typed was a '<code>.</code>'.
|
||||||
|
*
|
||||||
|
* @param tc The text component.
|
||||||
|
* @return Whether auto-activation would be appropriate.
|
||||||
|
*/
|
||||||
|
public boolean isAutoActivateOkay(JTextComponent tc);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the renderer to use when displaying completion choices.
|
||||||
|
*
|
||||||
|
* @param r The renderer to use.
|
||||||
|
* @see #getListCellRenderer()
|
||||||
|
*/
|
||||||
|
public void setListCellRenderer(ListCellRenderer r);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the values used to identify and insert "parameterized completions"
|
||||||
|
* (e.g. functions or methods). If this method isn't called, functions
|
||||||
|
* and methods will not have their parameters auto-completed.
|
||||||
|
*
|
||||||
|
* @param listStart The character that marks the beginning of a list of
|
||||||
|
* parameters, such as '<tt>(</tt>' in C or Java.
|
||||||
|
* @param separator Text that should separate parameters in a parameter
|
||||||
|
* list when one is inserted. For example, "<tt>, </tt>".
|
||||||
|
* @param listEnd The character that marks the end of a list of parameters,
|
||||||
|
* such as '<tt>)</tt>' in C or Java.
|
||||||
|
* @throws IllegalArgumentException If either <tt>listStart</tt> or
|
||||||
|
* <tt>listEnd</tt> is not printable ASCII, or if
|
||||||
|
* <tt>separator</tt> is <code>null</code> or an empty string.
|
||||||
|
* @see #clearParameterizedCompletionParams()
|
||||||
|
*/
|
||||||
|
public void setParameterizedCompletionParams(char listStart,
|
||||||
|
String separator, char listEnd);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the parent completion provider.
|
||||||
|
*
|
||||||
|
* @param parent The parent provider. <code>null</code> means there will
|
||||||
|
* be no parent provider.
|
||||||
|
* @see #getParent()
|
||||||
|
*/
|
||||||
|
public void setParent(CompletionProvider parent);
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,276 @@
|
|||||||
|
/*
|
||||||
|
* 02/06/2010
|
||||||
|
*
|
||||||
|
* CompletionProviderBase.java - Base completion provider implementation.
|
||||||
|
*
|
||||||
|
* This library is distributed under a modified BSD license. See the included
|
||||||
|
* AutoComplete.License.txt file for details.
|
||||||
|
*/
|
||||||
|
package org.fife.ui.autocomplete;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.List;
|
||||||
|
import javax.swing.ListCellRenderer;
|
||||||
|
import javax.swing.text.BadLocationException;
|
||||||
|
import javax.swing.text.Document;
|
||||||
|
import javax.swing.text.JTextComponent;
|
||||||
|
import javax.swing.text.Segment;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A base class for all standard completion providers. This class implements
|
||||||
|
* functionality that should be sharable across all <tt>CompletionProvider</tt>
|
||||||
|
* implementations.
|
||||||
|
*
|
||||||
|
* @author Robert Futrell
|
||||||
|
* @version 1.0
|
||||||
|
* @see AbstractCompletionProvider
|
||||||
|
*/
|
||||||
|
public abstract class CompletionProviderBase implements CompletionProvider {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The parent completion provider.
|
||||||
|
*/
|
||||||
|
private CompletionProvider parent;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The renderer to use for completions from this provider. If this is
|
||||||
|
* <code>null</code>, a default renderer is used.
|
||||||
|
*/
|
||||||
|
private ListCellRenderer listCellRenderer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Text that marks the beginning of a parameter list, for example, '('.
|
||||||
|
*/
|
||||||
|
private char paramListStart;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Text that marks the end of a parameter list, for example, ')'.
|
||||||
|
*/
|
||||||
|
private char paramListEnd;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Text that separates items in a parameter list, for example, ", ".
|
||||||
|
*/
|
||||||
|
private String paramListSeparator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether auto-activation should occur after letters.
|
||||||
|
*/
|
||||||
|
private boolean autoActivateAfterLetters;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Non-letter chars that should cause auto-activation to occur.
|
||||||
|
*/
|
||||||
|
private String autoActivateChars;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides completion choices for a parameterized completion's parameters.
|
||||||
|
*/
|
||||||
|
private ParameterChoicesProvider paramChoicesProvider;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A segment to use for fast char access.
|
||||||
|
*/
|
||||||
|
private Segment s = new Segment();
|
||||||
|
|
||||||
|
protected static final String EMPTY_STRING = "";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Comparator used to sort completions by their relevance before sorting
|
||||||
|
* them lexicographically.
|
||||||
|
*/
|
||||||
|
private static final Comparator<Completion> sortByRelevanceComparator =
|
||||||
|
new SortByRelevanceComparator();
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void clearParameterizedCompletionParams() {
|
||||||
|
paramListEnd = paramListStart = 0;
|
||||||
|
paramListSeparator = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public List<Completion> getCompletions(JTextComponent comp) {
|
||||||
|
|
||||||
|
List<Completion> completions = getCompletionsImpl(comp);
|
||||||
|
if (parent!=null) {
|
||||||
|
List<Completion> parentCompletions = parent.getCompletions(comp);
|
||||||
|
if (parentCompletions!=null) {
|
||||||
|
completions.addAll(parentCompletions);
|
||||||
|
Collections.sort(completions);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE: We can't sort by relevance prior to this; we need to have
|
||||||
|
// things alphabetical so we can easily narrow down completions to
|
||||||
|
// those starting with what was already typed.
|
||||||
|
if (/*sortByRelevance*/true) {
|
||||||
|
Collections.sort(completions, sortByRelevanceComparator);
|
||||||
|
}
|
||||||
|
|
||||||
|
return completions;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Does the dirty work of creating a list of completions.
|
||||||
|
*
|
||||||
|
* @param comp The text component to look in.
|
||||||
|
* @return The list of possible completions, or an empty list if there
|
||||||
|
* are none.
|
||||||
|
*/
|
||||||
|
protected abstract List<Completion> getCompletionsImpl(JTextComponent comp);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public ListCellRenderer getListCellRenderer() {
|
||||||
|
return listCellRenderer;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public ParameterChoicesProvider getParameterChoicesProvider() {
|
||||||
|
return paramChoicesProvider;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public char getParameterListEnd() {
|
||||||
|
return paramListEnd;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String getParameterListSeparator() {
|
||||||
|
return paramListSeparator;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public char getParameterListStart() {
|
||||||
|
return paramListStart;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public CompletionProvider getParent() {
|
||||||
|
return parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean isAutoActivateOkay(JTextComponent tc) {
|
||||||
|
Document doc = tc.getDocument();
|
||||||
|
char ch = 0;
|
||||||
|
try {
|
||||||
|
doc.getText(tc.getCaretPosition(), 1, s);
|
||||||
|
ch = s.first();
|
||||||
|
} catch (BadLocationException ble) { // Never happens
|
||||||
|
ble.printStackTrace();
|
||||||
|
}
|
||||||
|
return (autoActivateAfterLetters && Character.isLetter(ch)) ||
|
||||||
|
(autoActivateChars!=null && autoActivateChars.indexOf(ch)>-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the characters that auto-activation should occur after. A Java
|
||||||
|
* completion provider, for example, might want to set <code>others</code>
|
||||||
|
* to "<code>.</code>", to allow auto-activation for members of an object.
|
||||||
|
*
|
||||||
|
* @param letters Whether auto-activation should occur after any letter.
|
||||||
|
* @param others A string of (non-letter) chars that auto-activation should
|
||||||
|
* occur after. This may be <code>null</code>.
|
||||||
|
*/
|
||||||
|
public void setAutoActivationRules(boolean letters, String others) {
|
||||||
|
autoActivateAfterLetters = letters;
|
||||||
|
autoActivateChars = others;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the param choices provider. This is used when a user
|
||||||
|
* code-completes a parameterized completion, such as a function or method.
|
||||||
|
* For any parameter to the function/method, this object can return
|
||||||
|
* possible completions.
|
||||||
|
*
|
||||||
|
* @param pcp The parameter choices provider, or <code>null</code> for
|
||||||
|
* none.
|
||||||
|
* @see #getParameterChoicesProvider()
|
||||||
|
*/
|
||||||
|
public void setParameterChoicesProvider(ParameterChoicesProvider pcp) {
|
||||||
|
paramChoicesProvider = pcp;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void setListCellRenderer(ListCellRenderer r) {
|
||||||
|
listCellRenderer = r;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void setParameterizedCompletionParams(char listStart,
|
||||||
|
String separator, char listEnd) {
|
||||||
|
if (listStart<0x20 || listStart==0x7F) {
|
||||||
|
throw new IllegalArgumentException("Invalid listStart");
|
||||||
|
}
|
||||||
|
if (listEnd<0x20 || listEnd==0x7F) {
|
||||||
|
throw new IllegalArgumentException("Invalid listEnd");
|
||||||
|
}
|
||||||
|
if (separator==null || separator.length()==0) {
|
||||||
|
throw new IllegalArgumentException("Invalid separator");
|
||||||
|
}
|
||||||
|
paramListStart = listStart;
|
||||||
|
paramListSeparator = separator;
|
||||||
|
paramListEnd = listEnd;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void setParent(CompletionProvider parent) {
|
||||||
|
this.parent = parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,446 @@
|
|||||||
|
/*
|
||||||
|
* 02/06/2010
|
||||||
|
*
|
||||||
|
* CompletionXMLParser.java - Parses XML representing code completion for a
|
||||||
|
* C-like language.
|
||||||
|
*
|
||||||
|
* This library is distributed under a modified BSD license. See the included
|
||||||
|
* AutoComplete.License.txt file for details.
|
||||||
|
*/
|
||||||
|
package org.fife.ui.autocomplete;
|
||||||
|
|
||||||
|
import java.lang.reflect.Constructor;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import org.xml.sax.Attributes;
|
||||||
|
import org.xml.sax.InputSource;
|
||||||
|
import org.xml.sax.SAXException;
|
||||||
|
import org.xml.sax.SAXParseException;
|
||||||
|
import org.xml.sax.helpers.DefaultHandler;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parser for an XML file describing a procedural language such as C. XML
|
||||||
|
* files will be validated against the <code>CompletionXml.dtd</code> DTD
|
||||||
|
* found in this package.
|
||||||
|
*
|
||||||
|
* @author Robert Futrell
|
||||||
|
* @version 1.0
|
||||||
|
*/
|
||||||
|
public class CompletionXMLParser extends DefaultHandler {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The completions found after parsing the XML.
|
||||||
|
*/
|
||||||
|
private List<Completion> completions;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The provider we're getting completions for.
|
||||||
|
*/
|
||||||
|
private CompletionProvider provider;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The completion provider to use when loading classes, such as custom
|
||||||
|
* {@link FunctionCompletion}s.
|
||||||
|
*/
|
||||||
|
private ClassLoader completionCL;
|
||||||
|
|
||||||
|
|
||||||
|
private String name;
|
||||||
|
private String type;
|
||||||
|
private String returnType;
|
||||||
|
private StringBuilder returnValDesc;
|
||||||
|
private StringBuilder desc;
|
||||||
|
private String paramName;
|
||||||
|
private String paramType;
|
||||||
|
private StringBuilder paramDesc;
|
||||||
|
private List<ParameterizedCompletion.Parameter> params;
|
||||||
|
private String definedIn;
|
||||||
|
private boolean doingKeywords;
|
||||||
|
private boolean inKeyword;
|
||||||
|
private boolean gettingReturnValDesc;
|
||||||
|
private boolean gettingDesc;
|
||||||
|
private boolean gettingParams;
|
||||||
|
private boolean inParam;
|
||||||
|
private boolean gettingParamDesc;
|
||||||
|
private boolean inCompletionTypes;
|
||||||
|
private char paramStartChar;
|
||||||
|
private char paramEndChar;
|
||||||
|
private String paramSeparator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If specified in the XML, this class will be used instead of
|
||||||
|
* {@link FunctionCompletion} when appropriate. This class should extend
|
||||||
|
* <tt>FunctionCompletion</tt>, or stuff will break.
|
||||||
|
*/
|
||||||
|
private String funcCompletionType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The class loader to use to load custom completion classes, such as
|
||||||
|
* the one defined by {@link #funcCompletionType}. If this is
|
||||||
|
* <code>null</code>, then a default class loader is used. This field
|
||||||
|
* will usually be <code>null</code>.
|
||||||
|
*/
|
||||||
|
private static ClassLoader DEFAULT_COMPLETION_CLASS_LOADER;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
*
|
||||||
|
* @param provider The provider to get completions for.
|
||||||
|
* @see #reset(CompletionProvider)
|
||||||
|
*/
|
||||||
|
public CompletionXMLParser(CompletionProvider provider) {
|
||||||
|
this(provider, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
*
|
||||||
|
* @param provider The provider to get completions for.
|
||||||
|
* @param cl The class loader to use, if necessary, when loading classes
|
||||||
|
* from the XML (custom {@link FunctionCompletion}s, for example).
|
||||||
|
* This may be <code>null</code> if the default is to be used, or
|
||||||
|
* if the XML does not define specific classes for completion types.
|
||||||
|
* @see #reset(CompletionProvider)
|
||||||
|
*/
|
||||||
|
public CompletionXMLParser(CompletionProvider provider, ClassLoader cl) {
|
||||||
|
this.provider = provider;
|
||||||
|
this.completionCL = cl;
|
||||||
|
if (completionCL==null) {
|
||||||
|
// May also be null, but that's okay.
|
||||||
|
completionCL = DEFAULT_COMPLETION_CLASS_LOADER;
|
||||||
|
}
|
||||||
|
completions = new ArrayList<Completion>();
|
||||||
|
params = new ArrayList<ParameterizedCompletion.Parameter>(1);
|
||||||
|
desc = new StringBuilder();
|
||||||
|
paramDesc = new StringBuilder();
|
||||||
|
returnValDesc = new StringBuilder();
|
||||||
|
paramStartChar = paramEndChar = 0;
|
||||||
|
paramSeparator = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when character data inside an element is found.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void characters(char[] ch, int start, int length) {
|
||||||
|
if (gettingDesc) {
|
||||||
|
desc.append(ch, start, length);
|
||||||
|
}
|
||||||
|
else if (gettingParamDesc) {
|
||||||
|
paramDesc.append(ch, start, length);
|
||||||
|
}
|
||||||
|
else if (gettingReturnValDesc) {
|
||||||
|
returnValDesc.append(ch, start, length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private FunctionCompletion createFunctionCompletion() {
|
||||||
|
|
||||||
|
FunctionCompletion fc = null;
|
||||||
|
if (funcCompletionType!=null) {
|
||||||
|
try {
|
||||||
|
Class<?> clazz = null;
|
||||||
|
if (completionCL!=null) {
|
||||||
|
clazz = Class.forName(funcCompletionType, true,
|
||||||
|
completionCL);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
clazz = Class.forName(funcCompletionType);
|
||||||
|
}
|
||||||
|
Constructor<?> c = clazz.getDeclaredConstructor(
|
||||||
|
CompletionProvider.class, String.class, String.class);
|
||||||
|
fc = (FunctionCompletion)c.newInstance(provider, name,
|
||||||
|
returnType);
|
||||||
|
} catch (RuntimeException re) { // FindBugs
|
||||||
|
throw re;
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fc==null) { // Fallback if completion failed for some reason
|
||||||
|
fc = new FunctionCompletion(provider, name, returnType);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (desc.length()>0) {
|
||||||
|
fc.setShortDescription(desc.toString());
|
||||||
|
desc.setLength(0);
|
||||||
|
}
|
||||||
|
fc.setParams(params);
|
||||||
|
fc.setDefinedIn(definedIn);
|
||||||
|
if (returnValDesc.length()>0) {
|
||||||
|
fc.setReturnValueDescription(returnValDesc.toString());
|
||||||
|
returnValDesc.setLength(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
return fc;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private BasicCompletion createOtherCompletion() {
|
||||||
|
BasicCompletion bc = new BasicCompletion(provider, name);
|
||||||
|
if (desc.length()>0) {
|
||||||
|
bc.setSummary(desc.toString());
|
||||||
|
desc.setLength(0);
|
||||||
|
}
|
||||||
|
return bc;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private MarkupTagCompletion createMarkupTagCompletion() {
|
||||||
|
MarkupTagCompletion mc = new MarkupTagCompletion(provider,
|
||||||
|
name);
|
||||||
|
if (desc.length()>0) {
|
||||||
|
mc.setDescription(desc.toString());
|
||||||
|
desc.setLength(0);
|
||||||
|
}
|
||||||
|
mc.setAttributes(params);
|
||||||
|
mc.setDefinedIn(definedIn);
|
||||||
|
return mc;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private VariableCompletion createVariableCompletion() {
|
||||||
|
VariableCompletion vc = new VariableCompletion(provider,
|
||||||
|
name, returnType);
|
||||||
|
if (desc.length()>0) {
|
||||||
|
vc.setShortDescription(desc.toString());
|
||||||
|
desc.setLength(0);
|
||||||
|
}
|
||||||
|
vc.setDefinedIn(definedIn);
|
||||||
|
return vc;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when an element is closed.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void endElement(String uri, String localName, String qName) {
|
||||||
|
|
||||||
|
if ("keywords".equals(qName)) {
|
||||||
|
doingKeywords = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
else if (doingKeywords) {
|
||||||
|
|
||||||
|
if ("keyword".equals(qName)) {
|
||||||
|
Completion c = null;
|
||||||
|
if ("function".equals(type)) {
|
||||||
|
c = createFunctionCompletion();
|
||||||
|
}
|
||||||
|
else if ("constant".equals(type)) {
|
||||||
|
c = createVariableCompletion();
|
||||||
|
}
|
||||||
|
else if ("tag".equals(type)) { // Markup tag, such as HTML
|
||||||
|
c = createMarkupTagCompletion();
|
||||||
|
}
|
||||||
|
else if ("other".equals(type)) {
|
||||||
|
c = createOtherCompletion();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
throw new InternalError("Unexpected type: " + type);
|
||||||
|
}
|
||||||
|
completions.add(c);
|
||||||
|
inKeyword = false;
|
||||||
|
}
|
||||||
|
else if (inKeyword) {
|
||||||
|
if ("returnValDesc".equals(qName)) {
|
||||||
|
gettingReturnValDesc = false;
|
||||||
|
}
|
||||||
|
else if (gettingParams) {
|
||||||
|
if ("params".equals(qName)) {
|
||||||
|
gettingParams = false;
|
||||||
|
}
|
||||||
|
else if ("param".equals(qName)) {
|
||||||
|
FunctionCompletion.Parameter param =
|
||||||
|
new FunctionCompletion.Parameter(paramType,
|
||||||
|
paramName);
|
||||||
|
if (paramDesc.length()>0) {
|
||||||
|
param.setDescription(paramDesc.toString());
|
||||||
|
paramDesc.setLength(0);
|
||||||
|
}
|
||||||
|
params.add(param);
|
||||||
|
inParam = false;
|
||||||
|
}
|
||||||
|
else if (inParam) {
|
||||||
|
if ("desc".equals(qName)) {
|
||||||
|
gettingParamDesc = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if ("desc".equals(qName)) {
|
||||||
|
gettingDesc = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
else if (inCompletionTypes) {
|
||||||
|
if ("completionTypes".equals(qName)) {
|
||||||
|
inCompletionTypes = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void error(SAXParseException e) throws SAXException {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the completions found after parsing the XML.
|
||||||
|
*
|
||||||
|
* @return The completions.
|
||||||
|
*/
|
||||||
|
public List<Completion> getCompletions() {
|
||||||
|
return completions;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the parameter end character specified.
|
||||||
|
*
|
||||||
|
* @return The character, or 0 if none was specified.
|
||||||
|
*/
|
||||||
|
public char getParamEndChar() {
|
||||||
|
return paramEndChar;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the parameter end string specified.
|
||||||
|
*
|
||||||
|
* @return The string, or <code>null</code> if none was specified.
|
||||||
|
*/
|
||||||
|
public String getParamSeparator() {
|
||||||
|
return paramSeparator;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the parameter start character specified.
|
||||||
|
*
|
||||||
|
* @return The character, or 0 if none was specified.
|
||||||
|
*/
|
||||||
|
public char getParamStartChar() {
|
||||||
|
return paramStartChar;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static final char getSingleChar(String str) {
|
||||||
|
return str.length()==1 ? str.charAt(0) : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resets this parser to grab more completions.
|
||||||
|
*
|
||||||
|
* @param provider The new provider to get completions for.
|
||||||
|
*/
|
||||||
|
public void reset(CompletionProvider provider) {
|
||||||
|
this.provider = provider;
|
||||||
|
completions.clear();
|
||||||
|
doingKeywords = inKeyword = gettingDesc = gettingParams = inParam =
|
||||||
|
gettingParamDesc = false;
|
||||||
|
paramStartChar = paramEndChar = 0;
|
||||||
|
paramSeparator = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public InputSource resolveEntity(String publicID,
|
||||||
|
String systemID) throws SAXException {
|
||||||
|
return new InputSource(getClass().
|
||||||
|
getResourceAsStream("CompletionXml.dtd"));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the class loader to use when loading custom classes to use for
|
||||||
|
* various {@link Completion} types, such as {@link FunctionCompletion}s,
|
||||||
|
* from XML.<p>
|
||||||
|
*
|
||||||
|
* Users should very rarely have a need to use this method.
|
||||||
|
*
|
||||||
|
* @param cl The class loader to use. If this is <code>null</code>, then
|
||||||
|
* a default is used.
|
||||||
|
*/
|
||||||
|
public static void setDefaultCompletionClassLoader(ClassLoader cl) {
|
||||||
|
DEFAULT_COMPLETION_CLASS_LOADER = cl;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when an element starts.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void startElement(String uri, String localName, String qName,
|
||||||
|
Attributes attrs) {
|
||||||
|
if ("keywords".equals(qName)) {
|
||||||
|
doingKeywords = true;
|
||||||
|
}
|
||||||
|
else if (doingKeywords) {
|
||||||
|
if ("keyword".equals(qName)) {
|
||||||
|
name = attrs.getValue("name");
|
||||||
|
type = attrs.getValue("type");
|
||||||
|
returnType = attrs.getValue("returnType");
|
||||||
|
params.clear();
|
||||||
|
definedIn = attrs.getValue("definedIn");
|
||||||
|
inKeyword = true;
|
||||||
|
}
|
||||||
|
else if (inKeyword) {
|
||||||
|
if ("returnValDesc".equals(qName)) {
|
||||||
|
gettingReturnValDesc = true;
|
||||||
|
}
|
||||||
|
else if ("params".equals(qName)) {
|
||||||
|
gettingParams = true;
|
||||||
|
}
|
||||||
|
else if (gettingParams) {
|
||||||
|
if ("param".equals(qName)) {
|
||||||
|
paramName = attrs.getValue("name");
|
||||||
|
paramType = attrs.getValue("type");
|
||||||
|
inParam = true;
|
||||||
|
}
|
||||||
|
if (inParam) {
|
||||||
|
if ("desc".equals(qName)) {
|
||||||
|
gettingParamDesc = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if ("desc".equals(qName)) {
|
||||||
|
gettingDesc = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if ("environment".equals(qName)) {
|
||||||
|
paramStartChar = getSingleChar(attrs.getValue("paramStartChar"));
|
||||||
|
paramEndChar = getSingleChar(attrs.getValue("paramEndChar"));
|
||||||
|
paramSeparator = attrs.getValue("paramSeparator");
|
||||||
|
//paramTerminal = attrs.getValua("terminal");
|
||||||
|
}
|
||||||
|
else if ("completionTypes".equals(qName)) {
|
||||||
|
inCompletionTypes = true;
|
||||||
|
}
|
||||||
|
else if (inCompletionTypes) {
|
||||||
|
if ("functionCompletionType".equals(qName)) {
|
||||||
|
funcCompletionType = attrs.getValue("type");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void warning(SAXParseException e) throws SAXException {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,376 @@
|
|||||||
|
/*
|
||||||
|
* 12/21/2008
|
||||||
|
*
|
||||||
|
* DefaultCompletionProvider.java - A basic completion provider implementation.
|
||||||
|
*
|
||||||
|
* This library is distributed under a modified BSD license. See the included
|
||||||
|
* AutoComplete.License.txt file for details.
|
||||||
|
*/
|
||||||
|
package org.fife.ui.autocomplete;
|
||||||
|
|
||||||
|
import java.awt.Point;
|
||||||
|
import java.io.BufferedInputStream;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import javax.swing.text.BadLocationException;
|
||||||
|
import javax.swing.text.Document;
|
||||||
|
import javax.swing.text.Element;
|
||||||
|
import javax.swing.text.JTextComponent;
|
||||||
|
import javax.swing.text.Segment;
|
||||||
|
import javax.xml.parsers.ParserConfigurationException;
|
||||||
|
import javax.xml.parsers.SAXParser;
|
||||||
|
import javax.xml.parsers.SAXParserFactory;
|
||||||
|
import org.xml.sax.SAXException;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A basic completion provider implementation. This provider has no
|
||||||
|
* understanding of language semantics. It simply checks the text entered up
|
||||||
|
* to the caret position for a match against known completions. This is all
|
||||||
|
* that is needed in the majority of cases.
|
||||||
|
*
|
||||||
|
* @author Robert Futrell
|
||||||
|
* @version 1.0
|
||||||
|
*/
|
||||||
|
public class DefaultCompletionProvider extends AbstractCompletionProvider {
|
||||||
|
|
||||||
|
protected Segment seg;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to speed up {@link #getCompletionsAt(JTextComponent, Point)}.
|
||||||
|
*/
|
||||||
|
private String lastCompletionsAtText;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to speed up {@link #getCompletionsAt(JTextComponent, Point)},
|
||||||
|
* since this may be called multiple times in succession (this is usually
|
||||||
|
* called by <tt>JTextComponent.getToolTipText()</tt>, and if the user
|
||||||
|
* wiggles the mouse while a tool tip is displayed, this method gets
|
||||||
|
* repeatedly called. It can be costly so we try to speed it up a tad).
|
||||||
|
*/
|
||||||
|
private List<Completion> lastParameterizedCompletionsAt;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor. The returned provider will not be aware of any completions.
|
||||||
|
*
|
||||||
|
* @see #addCompletion(Completion)
|
||||||
|
*/
|
||||||
|
public DefaultCompletionProvider() {
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a completion provider that provides completion for a simple
|
||||||
|
* list of words.
|
||||||
|
*
|
||||||
|
* @param words The words to offer as completion suggestions. If this is
|
||||||
|
* <code>null</code>, no completions will be known.
|
||||||
|
* @see BasicCompletion
|
||||||
|
*/
|
||||||
|
public DefaultCompletionProvider(String[] words) {
|
||||||
|
init();
|
||||||
|
addWordCompletions(words);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the text just before the current caret position that could be
|
||||||
|
* the start of something auto-completable.<p>
|
||||||
|
*
|
||||||
|
* This method returns all characters before the caret that are matched
|
||||||
|
* by {@link #isValidChar(char)}.
|
||||||
|
*
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String getAlreadyEnteredText(JTextComponent comp) {
|
||||||
|
|
||||||
|
Document doc = comp.getDocument();
|
||||||
|
|
||||||
|
int dot = comp.getCaretPosition();
|
||||||
|
Element root = doc.getDefaultRootElement();
|
||||||
|
int index = root.getElementIndex(dot);
|
||||||
|
Element elem = root.getElement(index);
|
||||||
|
int start = elem.getStartOffset();
|
||||||
|
int len = dot-start;
|
||||||
|
try {
|
||||||
|
doc.getText(start, len, seg);
|
||||||
|
} catch (BadLocationException ble) {
|
||||||
|
ble.printStackTrace();
|
||||||
|
return EMPTY_STRING;
|
||||||
|
}
|
||||||
|
|
||||||
|
int segEnd = seg.offset + len;
|
||||||
|
start = segEnd - 1;
|
||||||
|
while (start>=seg.offset && isValidChar(seg.array[start])) {
|
||||||
|
start--;
|
||||||
|
}
|
||||||
|
start++;
|
||||||
|
|
||||||
|
len = segEnd - start;
|
||||||
|
return len==0 ? EMPTY_STRING : new String(seg.array, start, len);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public List<Completion> getCompletionsAt(JTextComponent tc, Point p) {
|
||||||
|
|
||||||
|
int offset = tc.viewToModel(p);
|
||||||
|
if (offset<0 || offset>=tc.getDocument().getLength()) {
|
||||||
|
lastCompletionsAtText = null;
|
||||||
|
return lastParameterizedCompletionsAt = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Segment s = new Segment();
|
||||||
|
Document doc = tc.getDocument();
|
||||||
|
Element root = doc.getDefaultRootElement();
|
||||||
|
int line = root.getElementIndex(offset);
|
||||||
|
Element elem = root.getElement(line);
|
||||||
|
int start = elem.getStartOffset();
|
||||||
|
int end = elem.getEndOffset() - 1;
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
doc.getText(start, end-start, s);
|
||||||
|
|
||||||
|
// Get the valid chars before the specified offset.
|
||||||
|
int startOffs = s.offset + (offset-start) - 1;
|
||||||
|
while (startOffs>=s.offset && isValidChar(s.array[startOffs])) {
|
||||||
|
startOffs--;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the valid chars at and after the specified offset.
|
||||||
|
int endOffs = s.offset + (offset-start);
|
||||||
|
while (endOffs<s.offset+s.count && isValidChar(s.array[endOffs])) {
|
||||||
|
endOffs++;
|
||||||
|
}
|
||||||
|
|
||||||
|
int len = endOffs - startOffs - 1;
|
||||||
|
if (len<=0) {
|
||||||
|
return lastParameterizedCompletionsAt = null;
|
||||||
|
}
|
||||||
|
String text = new String(s.array, startOffs+1, len);
|
||||||
|
|
||||||
|
if (text.equals(lastCompletionsAtText)) {
|
||||||
|
return lastParameterizedCompletionsAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get a list of all Completions matching the text.
|
||||||
|
List<Completion> list = getCompletionByInputText(text);
|
||||||
|
lastCompletionsAtText = text;
|
||||||
|
return lastParameterizedCompletionsAt = list;
|
||||||
|
|
||||||
|
} catch (BadLocationException ble) {
|
||||||
|
ble.printStackTrace(); // Never happens
|
||||||
|
}
|
||||||
|
|
||||||
|
lastCompletionsAtText = null;
|
||||||
|
return lastParameterizedCompletionsAt = null;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public List<ParameterizedCompletion> getParameterizedCompletions(
|
||||||
|
JTextComponent tc) {
|
||||||
|
|
||||||
|
List<ParameterizedCompletion> list = null;
|
||||||
|
|
||||||
|
// If this provider doesn't support parameterized completions,
|
||||||
|
// bail out now.
|
||||||
|
char paramListStart = getParameterListStart();
|
||||||
|
if (paramListStart==0) {
|
||||||
|
return list; // null
|
||||||
|
}
|
||||||
|
|
||||||
|
int dot = tc.getCaretPosition();
|
||||||
|
Segment s = new Segment();
|
||||||
|
Document doc = tc.getDocument();
|
||||||
|
Element root = doc.getDefaultRootElement();
|
||||||
|
int line = root.getElementIndex(dot);
|
||||||
|
Element elem = root.getElement(line);
|
||||||
|
int offs = elem.getStartOffset();
|
||||||
|
int len = dot - offs - 1/*paramListStart.length()*/;
|
||||||
|
if (len<=0) { // Not enough chars on line for a method.
|
||||||
|
return list; // null
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
doc.getText(offs, len, s);
|
||||||
|
|
||||||
|
// Get the identifier preceding the '(', ignoring any whitespace
|
||||||
|
// between them.
|
||||||
|
offs = s.offset + len - 1;
|
||||||
|
while (offs>=s.offset && Character.isWhitespace(s.array[offs])) {
|
||||||
|
offs--;
|
||||||
|
}
|
||||||
|
int end = offs;
|
||||||
|
while (offs>=s.offset && isValidChar(s.array[offs])) {
|
||||||
|
offs--;
|
||||||
|
}
|
||||||
|
|
||||||
|
String text = new String(s.array, offs+1, end-offs);
|
||||||
|
|
||||||
|
// Get a list of all Completions matching the text, but then
|
||||||
|
// narrow it down to just the ParameterizedCompletions.
|
||||||
|
List<Completion> l = getCompletionByInputText(text);
|
||||||
|
if (l!=null && !l.isEmpty()) {
|
||||||
|
for (int i=0; i<l.size(); i++) {
|
||||||
|
Object o = l.get(i);
|
||||||
|
if (o instanceof ParameterizedCompletion) {
|
||||||
|
if (list==null) {
|
||||||
|
list = new ArrayList<ParameterizedCompletion>(1);
|
||||||
|
}
|
||||||
|
list.add((ParameterizedCompletion)o);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (BadLocationException ble) {
|
||||||
|
ble.printStackTrace(); // Never happens
|
||||||
|
}
|
||||||
|
|
||||||
|
return list;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes this completion provider.
|
||||||
|
*/
|
||||||
|
protected void init() {
|
||||||
|
seg = new Segment();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether the specified character is valid in an auto-completion.
|
||||||
|
* The default implementation is equivalent to
|
||||||
|
* "<code>Character.isLetterOrDigit(ch) || ch=='_'</code>". Subclasses
|
||||||
|
* can override this method to change what characters are matched.
|
||||||
|
*
|
||||||
|
* @param ch The character.
|
||||||
|
* @return Whether the character is valid.
|
||||||
|
*/
|
||||||
|
protected boolean isValidChar(char ch) {
|
||||||
|
return Character.isLetterOrDigit(ch) || ch=='_';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads completions from an XML file. The XML should validate against
|
||||||
|
* <code>CompletionXml.dtd</code>.
|
||||||
|
*
|
||||||
|
* @param file An XML file to load from.
|
||||||
|
* @throws IOException If an IO error occurs.
|
||||||
|
*/
|
||||||
|
public void loadFromXML(File file) throws IOException {
|
||||||
|
BufferedInputStream bin = new BufferedInputStream(
|
||||||
|
new FileInputStream(file));
|
||||||
|
try {
|
||||||
|
loadFromXML(bin);
|
||||||
|
} finally {
|
||||||
|
bin.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads completions from an XML input stream. The XML should validate
|
||||||
|
* against <code>CompletionXml.dtd</code>.
|
||||||
|
*
|
||||||
|
* @param in The input stream to read from.
|
||||||
|
* @throws IOException If an IO error occurs.
|
||||||
|
*/
|
||||||
|
public void loadFromXML(InputStream in) throws IOException {
|
||||||
|
loadFromXML(in, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads completions from an XML input stream. The XML should validate
|
||||||
|
* against <code>CompletionXml.dtd</code>.
|
||||||
|
*
|
||||||
|
* @param in The input stream to read from.
|
||||||
|
* @param cl The class loader to use when loading any extra classes defined
|
||||||
|
* in the XML, such as custom {@link FunctionCompletion}s. This
|
||||||
|
* may be <code>null</code> if the default is to be used, or if no
|
||||||
|
* custom completions are defined in the XML.
|
||||||
|
* @throws IOException If an IO error occurs.
|
||||||
|
*/
|
||||||
|
public void loadFromXML(InputStream in, ClassLoader cl) throws IOException {
|
||||||
|
|
||||||
|
//long start = System.currentTimeMillis();
|
||||||
|
|
||||||
|
SAXParserFactory factory = SAXParserFactory.newInstance();
|
||||||
|
factory.setValidating(true);
|
||||||
|
CompletionXMLParser handler = new CompletionXMLParser(this, cl);
|
||||||
|
BufferedInputStream bin = new BufferedInputStream(in);
|
||||||
|
try {
|
||||||
|
SAXParser saxParser = factory.newSAXParser();
|
||||||
|
saxParser.parse(bin, handler);
|
||||||
|
List<Completion> completions = handler.getCompletions();
|
||||||
|
addCompletions(completions);
|
||||||
|
char startChar = handler.getParamStartChar();
|
||||||
|
if (startChar!=0) {
|
||||||
|
char endChar = handler.getParamEndChar();
|
||||||
|
String sep = handler.getParamSeparator();
|
||||||
|
if (endChar!=0 && sep!=null && sep.length()>0) { // Sanity
|
||||||
|
setParameterizedCompletionParams(startChar, sep, endChar);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (SAXException se) {
|
||||||
|
throw new IOException(se.toString());
|
||||||
|
} catch (ParserConfigurationException pce) {
|
||||||
|
throw new IOException(pce.toString());
|
||||||
|
} finally {
|
||||||
|
//long time = System.currentTimeMillis() - start;
|
||||||
|
//System.out.println("XML loaded in: " + time + "ms");
|
||||||
|
bin.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads completions from an XML file. The XML should validate against
|
||||||
|
* <code>CompletionXml.dtd</code>.
|
||||||
|
*
|
||||||
|
* @param resource A resource the current ClassLoader can get to.
|
||||||
|
* @throws IOException If an IO error occurs.
|
||||||
|
*/
|
||||||
|
public void loadFromXML(String resource) throws IOException {
|
||||||
|
ClassLoader cl = getClass().getClassLoader();
|
||||||
|
InputStream in = cl.getResourceAsStream(resource);
|
||||||
|
if (in==null) {
|
||||||
|
File file = new File(resource);
|
||||||
|
if (file.isFile()) {
|
||||||
|
in = new FileInputStream(file);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
throw new IOException("No such resource: " + resource);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
BufferedInputStream bin = new BufferedInputStream(in);
|
||||||
|
try {
|
||||||
|
loadFromXML(bin);
|
||||||
|
} finally {
|
||||||
|
bin.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,94 @@
|
|||||||
|
/*
|
||||||
|
* 12/22/2008
|
||||||
|
*
|
||||||
|
* DelegatingCellRenderer.java - A renderer for Completions that will delegate
|
||||||
|
* to the Completion's provider's renderer, if there is one.
|
||||||
|
*
|
||||||
|
* This library is distributed under a modified BSD license. See the included
|
||||||
|
* AutoComplete.License.txt file for details.
|
||||||
|
*/
|
||||||
|
package org.fife.ui.autocomplete;
|
||||||
|
|
||||||
|
import java.awt.Component;
|
||||||
|
import javax.swing.DefaultListCellRenderer;
|
||||||
|
import javax.swing.JComponent;
|
||||||
|
import javax.swing.JList;
|
||||||
|
import javax.swing.ListCellRenderer;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List cell renderer that delegates to a {@link CompletionProvider}'s
|
||||||
|
* renderer, if it has one. If it doesn't, it calls into a fallback renderer.
|
||||||
|
* If a fallback renderer isn't specified, it simply renders
|
||||||
|
* <code>(({@link Completion})value).toString()</code>.
|
||||||
|
*
|
||||||
|
* @author Robert Futrell
|
||||||
|
* @version 1.0
|
||||||
|
*/
|
||||||
|
class DelegatingCellRenderer extends DefaultListCellRenderer {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The renderer to fall back on if one isn't specified by a provider.
|
||||||
|
* This is usually <tt>this</tt>.
|
||||||
|
*/
|
||||||
|
private ListCellRenderer fallback;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the fallback cell renderer.
|
||||||
|
*
|
||||||
|
* @return The fallback cell renderer.
|
||||||
|
* @see #setFallbackCellRenderer(ListCellRenderer)
|
||||||
|
*/
|
||||||
|
public ListCellRenderer getFallbackCellRenderer() {
|
||||||
|
return fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Component getListCellRendererComponent(JList list, Object value,
|
||||||
|
int index, boolean selected, boolean hasFocus) {
|
||||||
|
Completion c = (Completion)value;
|
||||||
|
CompletionProvider p = c.getProvider();
|
||||||
|
ListCellRenderer r = p.getListCellRenderer();
|
||||||
|
if (r!=null) {
|
||||||
|
return r.getListCellRendererComponent(list, value, index, selected,
|
||||||
|
hasFocus);
|
||||||
|
}
|
||||||
|
if (fallback==null) {
|
||||||
|
return super.getListCellRendererComponent(list, value, index,
|
||||||
|
selected, hasFocus);
|
||||||
|
}
|
||||||
|
return fallback.getListCellRendererComponent(list, value, index,
|
||||||
|
selected, hasFocus);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the fallback cell renderer.
|
||||||
|
*
|
||||||
|
* @param fallback The fallback cell renderer. If this is
|
||||||
|
* <code>null</code>, <tt>this</tt> will be used.
|
||||||
|
* @see #getFallbackCellRenderer()
|
||||||
|
*/
|
||||||
|
public void setFallbackCellRenderer(ListCellRenderer fallback) {
|
||||||
|
this.fallback = fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void updateUI() {
|
||||||
|
super.updateUI();
|
||||||
|
if ((fallback instanceof JComponent) && fallback!=this) {
|
||||||
|
((JComponent)fallback).updateUI();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
/*
|
||||||
|
* 05/11/2012
|
||||||
|
*
|
||||||
|
* This library is distributed under a modified BSD license. See the included
|
||||||
|
* AutoComplete.License.txt file for details.
|
||||||
|
*/
|
||||||
|
package org.fife.ui.autocomplete;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Passed to {@link ExternalURLHandler}s as a way for them to display a summary
|
||||||
|
* for a new completion in response to a link event.
|
||||||
|
*
|
||||||
|
* @author Robert Futrell
|
||||||
|
* @version 1.0
|
||||||
|
* @see ExternalURLHandler
|
||||||
|
*/
|
||||||
|
public interface DescWindowCallback {
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback allowing a new code completion's description to be displayed
|
||||||
|
* in the description window.
|
||||||
|
*
|
||||||
|
* @param completion The new completion.
|
||||||
|
* @param anchor The anchor to scroll to, or <code>null</code> if none.
|
||||||
|
*/
|
||||||
|
public void showSummaryFor(Completion completion, String anchor);
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
/*
|
||||||
|
* 04/29/2010
|
||||||
|
*
|
||||||
|
* EmptyIcon.java - The canonical icon that paints nothing.
|
||||||
|
*
|
||||||
|
* This library is distributed under a modified BSD license. See the included
|
||||||
|
* AutoComplete.License.txt file for details.
|
||||||
|
*/
|
||||||
|
package org.fife.ui.autocomplete;
|
||||||
|
|
||||||
|
import java.awt.Component;
|
||||||
|
import java.awt.Graphics;
|
||||||
|
import java.io.Serializable;
|
||||||
|
import javax.swing.Icon;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A standard icon that doesn't paint anything. This can be used when some
|
||||||
|
* <code>Completion</code>s have icons and others don't, to visually align the
|
||||||
|
* text of all completions.
|
||||||
|
*
|
||||||
|
* @author Robert Futrell
|
||||||
|
* @version 1.0
|
||||||
|
*/
|
||||||
|
public class EmptyIcon implements Icon, Serializable {
|
||||||
|
|
||||||
|
private int size;
|
||||||
|
|
||||||
|
|
||||||
|
public EmptyIcon(int size) {
|
||||||
|
this.size = size;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getIconHeight() {
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getIconWidth() {
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void paintIcon(Component c, Graphics g, int x, int y) {
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
/*
|
||||||
|
* 12/23/2008
|
||||||
|
*
|
||||||
|
* ExternalURLHandler.java - Implementations can be registered as a callback
|
||||||
|
* to handle the user clicking on external URL's.
|
||||||
|
*
|
||||||
|
* This library is distributed under a modified BSD license. See the included
|
||||||
|
* AutoComplete.License.txt file for details.
|
||||||
|
*/
|
||||||
|
package org.fife.ui.autocomplete;
|
||||||
|
|
||||||
|
import javax.swing.event.HyperlinkEvent;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A callback for when an external URL is clicked in the description window.
|
||||||
|
* If no handler is installed, and if running in Java 6, the system default
|
||||||
|
* web browser is used to open the URL. If not running Java 6, nothing will
|
||||||
|
* happen. If you want browser support for pre-Java 6 JRE's, you will need
|
||||||
|
* to register one of these callbacks on your {@link AutoCompletion}, and
|
||||||
|
* open the URL in a web browser yourself.<p>
|
||||||
|
*
|
||||||
|
* Alternatively, folks implementing robust code completion support for a
|
||||||
|
* language might install an <code>ExternalURLHandler</code> to handle
|
||||||
|
* navigating through linked documentation of objects, functions, etc.
|
||||||
|
*
|
||||||
|
* @author Robert Futrell
|
||||||
|
* @version 1.0
|
||||||
|
* @see AutoCompletion#setExternalURLHandler(ExternalURLHandler)
|
||||||
|
*/
|
||||||
|
public interface ExternalURLHandler {
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when an external URL is clicked in the description window.
|
||||||
|
*
|
||||||
|
* @param e The event containing the hyperlink clicked.
|
||||||
|
* @param c The completion currently being displayed.
|
||||||
|
* @param callback Allows you to display new content in the description
|
||||||
|
* window.
|
||||||
|
*/
|
||||||
|
public void urlClicked(HyperlinkEvent e, Completion c,
|
||||||
|
DescWindowCallback callback);
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,189 @@
|
|||||||
|
/*
|
||||||
|
* 04/26/2010
|
||||||
|
*
|
||||||
|
* FastListUI.java - A JList UI implementation that computes the preferred size
|
||||||
|
* of all cells really fast, to facilitate lists of possibly thousands of items
|
||||||
|
* rendered with HTML, which is slow with BasicListUI extensions.
|
||||||
|
*
|
||||||
|
* This library is distributed under a modified BSD license. See the included
|
||||||
|
* AutoComplete.License.txt file for details.
|
||||||
|
*/
|
||||||
|
package org.fife.ui.autocomplete;
|
||||||
|
|
||||||
|
import java.awt.Color;
|
||||||
|
import java.awt.Dimension;
|
||||||
|
import java.awt.SystemColor;
|
||||||
|
import javax.swing.JViewport;
|
||||||
|
import javax.swing.ListCellRenderer;
|
||||||
|
import javax.swing.ListModel;
|
||||||
|
import javax.swing.UIManager;
|
||||||
|
import javax.swing.plaf.basic.BasicListUI;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A custom list UI, used by the completion choices list. If the number of
|
||||||
|
* completion choices is "large," it does a fast estimate of the preferred
|
||||||
|
* width and height of list items. This allows HTML renderers to be used (such
|
||||||
|
* as {@link CompletionCellRenderer}), with thousands of completion choices,
|
||||||
|
* with no performance penalty. With standard BasicListUI subclasses, this can
|
||||||
|
* cause very poor performance <b>each time</b> the list is displayed, which
|
||||||
|
* is bad for lists that are repeatedly hidden and re-displayed, such as
|
||||||
|
* completion choices. This is all because the calculation to get the
|
||||||
|
* preferred size of each list item, when it is displayed with HTML, is slow.
|
||||||
|
*
|
||||||
|
* @author Robert Futrell
|
||||||
|
* @version 1.0
|
||||||
|
*/
|
||||||
|
class FastListUI extends BasicListUI {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the selection background was overridden (usually because of
|
||||||
|
* Nimbus) so we know to manually uninstall the color we installed.
|
||||||
|
*/
|
||||||
|
private boolean overriddenBackground;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the selection foreground was overridden (usually because of
|
||||||
|
* Nimbus) so we know to manually uninstall the color we installed.
|
||||||
|
*/
|
||||||
|
private boolean overriddenForeground;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If there are more than this many completions in a single list, this
|
||||||
|
* UI will estimate the cell width and height needed for each item instead
|
||||||
|
* of computing it, for performance reasons.
|
||||||
|
*/
|
||||||
|
private static final int ESTIMATION_THRESHOLD = 200;
|
||||||
|
|
||||||
|
|
||||||
|
private Color determineSelectionBackground() {
|
||||||
|
Color c = UIManager.getColor("List.selectionBackground");
|
||||||
|
if (c==null) {
|
||||||
|
c = UIManager.getColor("nimbusSelectionBackground");
|
||||||
|
if (c==null) { // Not Nimbus, but still need a value - fallback
|
||||||
|
c = UIManager.getColor("textHighlight");
|
||||||
|
if (c==null) {
|
||||||
|
c = SystemColor.textHighlight;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Nimbus unfortunately requires a Color, not a ColorUIResource, for
|
||||||
|
// the background override to work. This causes this color to "stick"
|
||||||
|
// even if the LAF is changed to something else later. "c" here may
|
||||||
|
// actually be a ColorUIResource
|
||||||
|
return new Color(c.getRGB());//new ColorUIResource(c);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private Color determineSelectionForeground() {
|
||||||
|
Color c = UIManager.getColor("List.selectionForeground");
|
||||||
|
if (c==null) {
|
||||||
|
c = UIManager.getColor("nimbusSelectedText");
|
||||||
|
if (c==null) { // Not Nimbus, but still need a value - fallback
|
||||||
|
c = UIManager.getColor("textHighlightText");
|
||||||
|
if (c==null) {
|
||||||
|
c = SystemColor.textHighlightText;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Nimbus unfortunately requires Color, not ColorUIResource, and "c"
|
||||||
|
// may actually be a ColorUIResource
|
||||||
|
return new Color(c.getRGB());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Overridden to ensure we have selection background/foreground colors
|
||||||
|
* defined, even if we're in some weirdo LAF such as Nimbus which doesn't
|
||||||
|
* define them. Since FastListUI extends BasicListUI, we need these values
|
||||||
|
* to be defined.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected void installDefaults() {
|
||||||
|
|
||||||
|
super.installDefaults();
|
||||||
|
|
||||||
|
if (list.getSelectionBackground()==null) {
|
||||||
|
list.setSelectionBackground(determineSelectionBackground());
|
||||||
|
overriddenBackground = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (list.getSelectionForeground()==null) {
|
||||||
|
list.setSelectionForeground(determineSelectionForeground());
|
||||||
|
overriddenForeground = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Overridden to work around a Nimbus issue.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected void uninstallDefaults() {
|
||||||
|
|
||||||
|
super.uninstallDefaults();
|
||||||
|
|
||||||
|
if (overriddenBackground) {
|
||||||
|
list.setSelectionBackground(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (overriddenForeground) {
|
||||||
|
list.setSelectionForeground(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recalculates the cell width and height of each cell in the list. This
|
||||||
|
* method is overridden to do a fast estimation if the completion list is
|
||||||
|
* too long, to improve performance for lists with huge amounts of
|
||||||
|
* completions.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected void updateLayoutState() {
|
||||||
|
|
||||||
|
ListModel model = list.getModel();
|
||||||
|
int itemCount = model.getSize();
|
||||||
|
|
||||||
|
// If the item count is small enough to run fast on practically all
|
||||||
|
// machines, go ahead and use the super implementation to determine
|
||||||
|
// the optimal cell sizes.
|
||||||
|
if (itemCount<ESTIMATION_THRESHOLD) {
|
||||||
|
super.updateLayoutState();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise, assume all cells are the same height as the first cell,
|
||||||
|
// and estimate the necessary width.
|
||||||
|
|
||||||
|
ListCellRenderer renderer = list.getCellRenderer();
|
||||||
|
|
||||||
|
cellWidth = list.getWidth();
|
||||||
|
if (list.getParent() instanceof JViewport) { // Always true for us
|
||||||
|
cellWidth = list.getParent().getWidth();
|
||||||
|
}
|
||||||
|
//System.out.println(cellWidth);
|
||||||
|
|
||||||
|
// We're getting a fixed cell height for all cells
|
||||||
|
cellHeights = null;
|
||||||
|
|
||||||
|
if (renderer!=null && itemCount>0) {
|
||||||
|
Object value = model.getElementAt(0);
|
||||||
|
java.awt.Component c = renderer.getListCellRendererComponent(list,
|
||||||
|
value, 0, false, false);
|
||||||
|
rendererPane.add(c);
|
||||||
|
Dimension cellSize = c.getPreferredSize();
|
||||||
|
cellHeight = cellSize.height;
|
||||||
|
cellWidth = Math.max(cellWidth, cellSize.width);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
cellHeight = 20;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,399 @@
|
|||||||
|
/*
|
||||||
|
* 12/22/2008
|
||||||
|
*
|
||||||
|
* FunctionCompletion.java - A completion representing a function.
|
||||||
|
*
|
||||||
|
* This library is distributed under a modified BSD license. See the included
|
||||||
|
* AutoComplete.License.txt file for details.
|
||||||
|
*/
|
||||||
|
package org.fife.ui.autocomplete;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import javax.swing.text.BadLocationException;
|
||||||
|
import javax.swing.text.JTextComponent;
|
||||||
|
import javax.swing.text.Position;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A completion choice representing a function.
|
||||||
|
*
|
||||||
|
* @author Robert Futrell
|
||||||
|
* @version 1.0
|
||||||
|
*/
|
||||||
|
public class FunctionCompletion extends VariableCompletion
|
||||||
|
implements ParameterizedCompletion {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parameters to the function.
|
||||||
|
*/
|
||||||
|
private List<Parameter> params;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A description of the return value of this function.
|
||||||
|
*/
|
||||||
|
private String returnValDesc;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to improve performance of sorting FunctionCompletions.
|
||||||
|
*/
|
||||||
|
private String compareString;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
*
|
||||||
|
* @param provider The parent provider.
|
||||||
|
* @param name The name of this function.
|
||||||
|
* @param returnType The return type of this function.
|
||||||
|
*/
|
||||||
|
public FunctionCompletion(CompletionProvider provider, String name,
|
||||||
|
String returnType) {
|
||||||
|
super(provider, name, returnType);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void addDefinitionString(StringBuilder sb) {
|
||||||
|
sb.append("<html><b>");
|
||||||
|
sb.append(getDefinitionString());
|
||||||
|
sb.append("</b>");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds HTML describing the parameters to this function to a buffer.
|
||||||
|
*
|
||||||
|
* @param sb The buffer to append to.
|
||||||
|
*/
|
||||||
|
protected void addParameters(StringBuilder sb) {
|
||||||
|
|
||||||
|
// TODO: Localize me
|
||||||
|
|
||||||
|
int paramCount = getParamCount();
|
||||||
|
if (paramCount>0) {
|
||||||
|
sb.append("<b>Parameters:</b><br>");
|
||||||
|
sb.append("<center><table width='90%'><tr><td>");
|
||||||
|
for (int i=0; i<paramCount; i++) {
|
||||||
|
Parameter param = getParam(i);
|
||||||
|
sb.append("<b>");
|
||||||
|
sb.append(param.getName()!=null ? param.getName() :
|
||||||
|
param.getType());
|
||||||
|
sb.append("</b> ");
|
||||||
|
String desc = param.getDescription();
|
||||||
|
if (desc!=null) {
|
||||||
|
sb.append(desc);
|
||||||
|
}
|
||||||
|
sb.append("<br>");
|
||||||
|
}
|
||||||
|
sb.append("</td></tr></table></center><br><br>");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (returnValDesc!=null) {
|
||||||
|
sb.append("<b>Returns:</b><br><center><table width='90%'><tr><td>");
|
||||||
|
sb.append(returnValDesc);
|
||||||
|
sb.append("</td></tr></table></center><br><br>");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Overridden to compare methods by their comparison strings.
|
||||||
|
*
|
||||||
|
* @param c2 A <code>Completion</code> to compare to.
|
||||||
|
* @return The sort order.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public int compareTo(Completion c2) {
|
||||||
|
|
||||||
|
int rc = -1;
|
||||||
|
|
||||||
|
if (c2==this) {
|
||||||
|
rc = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
else if (c2 instanceof FunctionCompletion) {
|
||||||
|
rc = getCompareString().compareTo(
|
||||||
|
((FunctionCompletion)c2).getCompareString());
|
||||||
|
}
|
||||||
|
|
||||||
|
else {
|
||||||
|
rc = super.compareTo(c2);
|
||||||
|
}
|
||||||
|
|
||||||
|
return rc;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a string used to compare this method completion to another.
|
||||||
|
*
|
||||||
|
* @return The comparison string.
|
||||||
|
*/
|
||||||
|
private String getCompareString() {
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This string compares the following parts of methods in this order,
|
||||||
|
* to optimize sort order in completion lists.
|
||||||
|
*
|
||||||
|
* 1. First, by name
|
||||||
|
* 2. Next, by number of parameters.
|
||||||
|
* 3. Finally, by parameter type.
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (compareString==null) {
|
||||||
|
StringBuilder sb = new StringBuilder(getName());
|
||||||
|
// NOTE: This will fail if a method has > 99 parameters (!)
|
||||||
|
int paramCount = getParamCount();
|
||||||
|
if (paramCount<10) {
|
||||||
|
sb.append('0');
|
||||||
|
}
|
||||||
|
sb.append(paramCount);
|
||||||
|
for (int i=0; i<paramCount; i++) {
|
||||||
|
String type = getParam(i).getType();
|
||||||
|
sb.append(type);
|
||||||
|
if (i<paramCount-1) {
|
||||||
|
sb.append(',');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
compareString = sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
return compareString;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the "definition string" for this function completion. For
|
||||||
|
* example, for the C "<code>printf</code>" function, this would return
|
||||||
|
* "<code>int printf(const char *, ...)</code>".
|
||||||
|
*
|
||||||
|
* @return The definition string.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String getDefinitionString() {
|
||||||
|
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
|
||||||
|
// Add the return type if applicable (C macros like NULL have no type).
|
||||||
|
String type = getType();
|
||||||
|
if (type!=null) {
|
||||||
|
sb.append(type).append(' ');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the item being described's name.
|
||||||
|
sb.append(getName());
|
||||||
|
|
||||||
|
// Add parameters for functions.
|
||||||
|
CompletionProvider provider = getProvider();
|
||||||
|
char start = provider.getParameterListStart();
|
||||||
|
if (start!=0) {
|
||||||
|
sb.append(start);
|
||||||
|
}
|
||||||
|
for (int i=0; i<getParamCount(); i++) {
|
||||||
|
Parameter param = getParam(i);
|
||||||
|
type = param.getType();
|
||||||
|
String name = param.getName();
|
||||||
|
if (type!=null) {
|
||||||
|
sb.append(type);
|
||||||
|
if (name!=null) {
|
||||||
|
sb.append(' ');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (name!=null) {
|
||||||
|
sb.append(name);
|
||||||
|
}
|
||||||
|
if (i<params.size()-1) {
|
||||||
|
sb.append(provider.getParameterListSeparator());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
char end = provider.getParameterListEnd();
|
||||||
|
if (end!=0) {
|
||||||
|
sb.append(end);
|
||||||
|
}
|
||||||
|
|
||||||
|
return sb.toString();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ParameterizedCompletionInsertionInfo getInsertionInfo(
|
||||||
|
JTextComponent tc, boolean replaceTabsWithSpaces) {
|
||||||
|
|
||||||
|
ParameterizedCompletionInsertionInfo info =
|
||||||
|
new ParameterizedCompletionInsertionInfo();
|
||||||
|
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
char paramListStart = getProvider().getParameterListStart();
|
||||||
|
if (paramListStart!='\0') {
|
||||||
|
sb.append(paramListStart);
|
||||||
|
}
|
||||||
|
int dot = tc.getCaretPosition() + sb.length();
|
||||||
|
int paramCount = getParamCount();
|
||||||
|
|
||||||
|
// Get the range in which the caret can move before we hide
|
||||||
|
// this tool tip.
|
||||||
|
int minPos = dot;
|
||||||
|
Position maxPos = null;
|
||||||
|
try {
|
||||||
|
maxPos = tc.getDocument().createPosition(dot-sb.length()+1);
|
||||||
|
} catch (BadLocationException ble) {
|
||||||
|
ble.printStackTrace(); // Never happens
|
||||||
|
}
|
||||||
|
info.setCaretRange(minPos, maxPos);
|
||||||
|
int firstParamLen = 0;
|
||||||
|
|
||||||
|
// Create the text to insert (keep it one completion for
|
||||||
|
// performance and simplicity of undo/redo).
|
||||||
|
int start = dot;
|
||||||
|
for (int i=0; i<paramCount; i++) {
|
||||||
|
Parameter param = getParam(i);
|
||||||
|
String paramText = getParamText(param);
|
||||||
|
if (i==0) {
|
||||||
|
firstParamLen = paramText.length();
|
||||||
|
}
|
||||||
|
sb.append(paramText);
|
||||||
|
int end = start + paramText.length();
|
||||||
|
info.addReplacementLocation(start, end);
|
||||||
|
// Patch for param. list separators with length > 2 -
|
||||||
|
// thanks to Matthew Adereth!
|
||||||
|
String sep = getProvider().getParameterListSeparator();
|
||||||
|
if (i<paramCount-1 && sep!=null) {
|
||||||
|
sb.append(sep);
|
||||||
|
start = end + sep.length();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sb.append(getProvider().getParameterListEnd());
|
||||||
|
int endOffs = dot + sb.length();
|
||||||
|
endOffs -= 1;//getProvider().getParameterListStart().length();
|
||||||
|
info.addReplacementLocation(endOffs, endOffs); // offset after function
|
||||||
|
info.setDefaultEndOffs(endOffs);
|
||||||
|
|
||||||
|
int selectionEnd = paramCount>0 ? (dot+firstParamLen) : dot;
|
||||||
|
info.setInitialSelection(dot, selectionEnd);
|
||||||
|
info.setTextToInsert(sb.toString());
|
||||||
|
return info;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Parameter getParam(int index) {
|
||||||
|
return params.get(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the number of parameters to this function.
|
||||||
|
*
|
||||||
|
* @return The number of parameters to this function.
|
||||||
|
* @see #getParam(int)
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public int getParamCount() {
|
||||||
|
return params==null ? 0 : params.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean getShowParameterToolTip() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the text to insert for a parameter.
|
||||||
|
*
|
||||||
|
* @param param The parameter.
|
||||||
|
* @return The text.
|
||||||
|
*/
|
||||||
|
private String getParamText(ParameterizedCompletion.Parameter param) {
|
||||||
|
String text = param.getName();
|
||||||
|
if (text==null) {
|
||||||
|
text = param.getType();
|
||||||
|
if (text==null) { // Shouldn't ever happen
|
||||||
|
text = "arg";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the description of the return value of this function.
|
||||||
|
*
|
||||||
|
* @return The description, or <code>null</code> if there is none.
|
||||||
|
* @see #setReturnValueDescription(String)
|
||||||
|
*/
|
||||||
|
public String getReturnValueDescription() {
|
||||||
|
return returnValDesc;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String getSummary() {
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
addDefinitionString(sb);
|
||||||
|
if (!possiblyAddDescription(sb)) {
|
||||||
|
sb.append("<br><br><br>");
|
||||||
|
}
|
||||||
|
addParameters(sb);
|
||||||
|
possiblyAddDefinedIn(sb);
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String getToolTipText() {
|
||||||
|
String text = getSummary();
|
||||||
|
if (text==null) {
|
||||||
|
text = getDefinitionString();
|
||||||
|
}
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the parameters to this function.
|
||||||
|
*
|
||||||
|
* @param params The parameters. This should be a list of
|
||||||
|
* {@link ParameterizedCompletion.Parameter}s.
|
||||||
|
* @see #getParam(int)
|
||||||
|
* @see #getParamCount()
|
||||||
|
*/
|
||||||
|
public void setParams(List<Parameter> params) {
|
||||||
|
if (params!=null) {
|
||||||
|
// Deep copy so parsing can re-use its array.
|
||||||
|
this.params = new ArrayList<Parameter>(params);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the description of the return value of this function.
|
||||||
|
*
|
||||||
|
* @param desc The description.
|
||||||
|
* @see #getReturnValueDescription()
|
||||||
|
*/
|
||||||
|
public void setReturnValueDescription(String desc) {
|
||||||
|
this.returnValDesc = desc;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,431 @@
|
|||||||
|
/*
|
||||||
|
* 01/03/2009
|
||||||
|
*
|
||||||
|
* LanguageAwareCompletionProvider.java - A completion provider that is aware
|
||||||
|
* of the language it is working with.
|
||||||
|
*
|
||||||
|
* This library is distributed under a modified BSD license. See the included
|
||||||
|
* AutoComplete.License.txt file for details.
|
||||||
|
*/
|
||||||
|
package org.fife.ui.autocomplete;
|
||||||
|
|
||||||
|
import java.awt.Point;
|
||||||
|
import java.awt.event.MouseEvent;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import javax.swing.text.JTextComponent;
|
||||||
|
|
||||||
|
import org.fife.ui.rsyntaxtextarea.RSyntaxDocument;
|
||||||
|
import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea;
|
||||||
|
import org.fife.ui.rsyntaxtextarea.RSyntaxUtilities;
|
||||||
|
import org.fife.ui.rsyntaxtextarea.Token;
|
||||||
|
import org.fife.ui.rtextarea.RTextArea;
|
||||||
|
import org.fife.ui.rtextarea.ToolTipSupplier;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A completion provider for the C programming language (and other languages
|
||||||
|
* with similar syntax). This provider simply delegates to another provider,
|
||||||
|
* depending on whether the caret is in:
|
||||||
|
*
|
||||||
|
* <ul>
|
||||||
|
* <li>Code (plain text)</li>
|
||||||
|
* <li>A string</li>
|
||||||
|
* <li>A comment</li>
|
||||||
|
* <li>A documentation comment</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* This allows for different completion choices in comments than in code,
|
||||||
|
* for example.<p>
|
||||||
|
*
|
||||||
|
* This provider also implements the
|
||||||
|
* <tt>org.fife.ui.rtextarea.ToolTipSupplier</tt> interface, which allows it
|
||||||
|
* to display tooltips for completion choices. Thus the standard
|
||||||
|
* {@link VariableCompletion} and {@link FunctionCompletion} completions should
|
||||||
|
* be able to display tooltips with the variable declaration or function
|
||||||
|
* definition (provided the <tt>RSyntaxTextArea</tt> was registered with the
|
||||||
|
* <tt>javax.swing.ToolTipManager</tt>).
|
||||||
|
*
|
||||||
|
* @author Robert Futrell
|
||||||
|
* @version 1.0
|
||||||
|
*/
|
||||||
|
public class LanguageAwareCompletionProvider extends CompletionProviderBase
|
||||||
|
implements ToolTipSupplier {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The provider to use when no provider is assigned to a particular token
|
||||||
|
* type.
|
||||||
|
*/
|
||||||
|
private CompletionProvider defaultProvider;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The provider to use when completing in a string.
|
||||||
|
*/
|
||||||
|
private CompletionProvider stringCompletionProvider;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The provider to use when completing in a comment.
|
||||||
|
*/
|
||||||
|
private CompletionProvider commentCompletionProvider;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The provider to use while in documentation comments.
|
||||||
|
*/
|
||||||
|
private CompletionProvider docCommentCompletionProvider;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor subclasses can use when they don't have their default
|
||||||
|
* provider created at construction time. They should call
|
||||||
|
* {@link #setDefaultCompletionProvider(CompletionProvider)} in this
|
||||||
|
* constructor.
|
||||||
|
*/
|
||||||
|
protected LanguageAwareCompletionProvider() {
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
*
|
||||||
|
* @param defaultProvider The provider to use when no provider is assigned
|
||||||
|
* to a particular token type. This cannot be <code>null</code>.
|
||||||
|
*/
|
||||||
|
public LanguageAwareCompletionProvider(CompletionProvider defaultProvider) {
|
||||||
|
setDefaultCompletionProvider(defaultProvider);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calling this method will result in an
|
||||||
|
* {@link UnsupportedOperationException} being thrown. To set the
|
||||||
|
* parameter completion parameters, do so on the provider returned by
|
||||||
|
* {@link #getDefaultCompletionProvider()}.
|
||||||
|
*
|
||||||
|
* @throws UnsupportedOperationException Always.
|
||||||
|
* @see #setParameterizedCompletionParams(char, String, char)
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void clearParameterizedCompletionParams() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String getAlreadyEnteredText(JTextComponent comp) {
|
||||||
|
if (!(comp instanceof RSyntaxTextArea)) {
|
||||||
|
return EMPTY_STRING;
|
||||||
|
}
|
||||||
|
CompletionProvider provider = getProviderFor(comp);
|
||||||
|
return provider!=null ? provider.getAlreadyEnteredText(comp) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the completion provider to use for comments.
|
||||||
|
*
|
||||||
|
* @return The completion provider to use.
|
||||||
|
* @see #setCommentCompletionProvider(CompletionProvider)
|
||||||
|
*/
|
||||||
|
public CompletionProvider getCommentCompletionProvider() {
|
||||||
|
return commentCompletionProvider;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public List<Completion> getCompletionsAt(JTextComponent tc, Point p) {
|
||||||
|
return defaultProvider==null ? null :
|
||||||
|
defaultProvider.getCompletionsAt(tc, p);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Does the dirty work of creating a list of completions.
|
||||||
|
*
|
||||||
|
* @param comp The text component to look in.
|
||||||
|
* @return The list of possible completions, or an empty list if there
|
||||||
|
* are none.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected List<Completion> getCompletionsImpl(JTextComponent comp) {
|
||||||
|
if (comp instanceof RSyntaxTextArea) {
|
||||||
|
CompletionProvider provider = getProviderFor(comp);
|
||||||
|
if (provider!=null) {
|
||||||
|
return provider.getCompletions(comp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the completion provider used when one isn't defined for a
|
||||||
|
* particular token type.
|
||||||
|
*
|
||||||
|
* @return The completion provider to use.
|
||||||
|
* @see #setDefaultCompletionProvider(CompletionProvider)
|
||||||
|
*/
|
||||||
|
public CompletionProvider getDefaultCompletionProvider() {
|
||||||
|
return defaultProvider;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the completion provider to use for documentation comments.
|
||||||
|
*
|
||||||
|
* @return The completion provider to use.
|
||||||
|
* @see #setDocCommentCompletionProvider(CompletionProvider)
|
||||||
|
*/
|
||||||
|
public CompletionProvider getDocCommentCompletionProvider() {
|
||||||
|
return docCommentCompletionProvider;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public List<ParameterizedCompletion> getParameterizedCompletions(
|
||||||
|
JTextComponent tc) {
|
||||||
|
// Parameterized completions can only come from the "code" completion
|
||||||
|
// provider. We do not do function/method completions while editing
|
||||||
|
// strings or comments.
|
||||||
|
CompletionProvider provider = getProviderFor(tc);
|
||||||
|
return provider==defaultProvider ?
|
||||||
|
provider.getParameterizedCompletions(tc) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public char getParameterListEnd() {
|
||||||
|
return defaultProvider.getParameterListEnd();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String getParameterListSeparator() {
|
||||||
|
return defaultProvider.getParameterListSeparator();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public char getParameterListStart() {
|
||||||
|
return defaultProvider.getParameterListStart();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the completion provider to use at the current caret position in
|
||||||
|
* a text component.
|
||||||
|
*
|
||||||
|
* @param comp The text component to check.
|
||||||
|
* @return The completion provider to use.
|
||||||
|
*/
|
||||||
|
private CompletionProvider getProviderFor(JTextComponent comp) {
|
||||||
|
|
||||||
|
RSyntaxTextArea rsta = (RSyntaxTextArea)comp;
|
||||||
|
RSyntaxDocument doc = (RSyntaxDocument)rsta.getDocument();
|
||||||
|
int line = rsta.getCaretLineNumber();
|
||||||
|
Token t = doc.getTokenListForLine(line);
|
||||||
|
if (t==null) {
|
||||||
|
return getDefaultCompletionProvider();
|
||||||
|
}
|
||||||
|
|
||||||
|
int dot = rsta.getCaretPosition();
|
||||||
|
Token curToken = RSyntaxUtilities.getTokenAtOffset(t, dot);
|
||||||
|
|
||||||
|
if (curToken==null) { // At end of the line
|
||||||
|
|
||||||
|
int type = doc.getLastTokenTypeOnLine(line);
|
||||||
|
if (type==Token.NULL) {
|
||||||
|
Token temp = t.getLastPaintableToken();
|
||||||
|
if (temp==null) {
|
||||||
|
return getDefaultCompletionProvider();
|
||||||
|
}
|
||||||
|
type = temp.getType();
|
||||||
|
}
|
||||||
|
|
||||||
|
// TokenMakers can use types < 0 for "internal types." This
|
||||||
|
// gives them a chance to map their internal types back to "real"
|
||||||
|
// types to get completion providers.
|
||||||
|
else if (type<0) {
|
||||||
|
type = doc.getClosestStandardTokenTypeForInternalType(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case Token.ERROR_STRING_DOUBLE:
|
||||||
|
return getStringCompletionProvider();
|
||||||
|
case Token.COMMENT_EOL:
|
||||||
|
case Token.COMMENT_MULTILINE:
|
||||||
|
return getCommentCompletionProvider();
|
||||||
|
case Token.COMMENT_DOCUMENTATION:
|
||||||
|
return getDocCommentCompletionProvider();
|
||||||
|
default:
|
||||||
|
return getDefaultCompletionProvider();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME: This isn't always a safe assumption.
|
||||||
|
if (dot==curToken.getOffset()) { // At the very beginning of a new token
|
||||||
|
// Need to check previous token for its type before deciding.
|
||||||
|
// Previous token may also be on previous line!
|
||||||
|
return getDefaultCompletionProvider();
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (curToken.getType()) {
|
||||||
|
case Token.LITERAL_STRING_DOUBLE_QUOTE:
|
||||||
|
case Token.ERROR_STRING_DOUBLE:
|
||||||
|
return getStringCompletionProvider();
|
||||||
|
case Token.COMMENT_EOL:
|
||||||
|
case Token.COMMENT_MULTILINE:
|
||||||
|
return getCommentCompletionProvider();
|
||||||
|
case Token.COMMENT_DOCUMENTATION:
|
||||||
|
return getDocCommentCompletionProvider();
|
||||||
|
case Token.NULL:
|
||||||
|
case Token.WHITESPACE:
|
||||||
|
case Token.IDENTIFIER:
|
||||||
|
case Token.VARIABLE:
|
||||||
|
case Token.PREPROCESSOR:
|
||||||
|
case Token.DATA_TYPE:
|
||||||
|
case Token.FUNCTION:
|
||||||
|
case Token.OPERATOR:
|
||||||
|
return getDefaultCompletionProvider();
|
||||||
|
}
|
||||||
|
|
||||||
|
return null; // In a token type we can't auto-complete from.
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the completion provider to use for strings.
|
||||||
|
*
|
||||||
|
* @return The completion provider to use.
|
||||||
|
* @see #setStringCompletionProvider(CompletionProvider)
|
||||||
|
*/
|
||||||
|
public CompletionProvider getStringCompletionProvider() {
|
||||||
|
return stringCompletionProvider;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean isAutoActivateOkay(JTextComponent tc) {
|
||||||
|
CompletionProvider provider = getProviderFor(tc);
|
||||||
|
return provider!=null ? provider.isAutoActivateOkay(tc) : false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the comment completion provider.
|
||||||
|
*
|
||||||
|
* @param provider The provider to use in comments.
|
||||||
|
* @see #getCommentCompletionProvider()
|
||||||
|
*/
|
||||||
|
public void setCommentCompletionProvider(CompletionProvider provider) {
|
||||||
|
this.commentCompletionProvider = provider;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the default completion provider.
|
||||||
|
*
|
||||||
|
* @param provider The provider to use when no provider is assigned to a
|
||||||
|
* particular token type. This cannot be <code>null</code>.
|
||||||
|
* @see #getDefaultCompletionProvider()
|
||||||
|
*/
|
||||||
|
public void setDefaultCompletionProvider(CompletionProvider provider) {
|
||||||
|
if (provider==null) {
|
||||||
|
throw new IllegalArgumentException("provider cannot be null");
|
||||||
|
}
|
||||||
|
this.defaultProvider = provider;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the documentation comment completion provider.
|
||||||
|
*
|
||||||
|
* @param provider The provider to use in comments.
|
||||||
|
* @see #getDocCommentCompletionProvider()
|
||||||
|
*/
|
||||||
|
public void setDocCommentCompletionProvider(CompletionProvider provider) {
|
||||||
|
this.docCommentCompletionProvider = provider;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calling this method will result in an
|
||||||
|
* {@link UnsupportedOperationException} being thrown. To set the
|
||||||
|
* parameter completion parameters, do so on the provider returned by
|
||||||
|
* {@link #getDefaultCompletionProvider()}.
|
||||||
|
*
|
||||||
|
* @throws UnsupportedOperationException Always.
|
||||||
|
* @see #clearParameterizedCompletionParams()
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void setParameterizedCompletionParams(char listStart,
|
||||||
|
String separator, char listEnd) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the completion provider to use while in a string.
|
||||||
|
*
|
||||||
|
* @param provider The provider to use.
|
||||||
|
* @see #getStringCompletionProvider()
|
||||||
|
*/
|
||||||
|
public void setStringCompletionProvider(CompletionProvider provider) {
|
||||||
|
stringCompletionProvider = provider;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the tool tip to display for a mouse event.<p>
|
||||||
|
*
|
||||||
|
* For this method to be called, the <tt>RSyntaxTextArea</tt> must be
|
||||||
|
* registered with the <tt>javax.swing.ToolTipManager</tt> like so:
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* ToolTipManager.sharedInstance().registerComponent(textArea);
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* @param textArea The text area.
|
||||||
|
* @param e The mouse event.
|
||||||
|
* @return The tool tip text, or <code>null</code> if none.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String getToolTipText(RTextArea textArea, MouseEvent e) {
|
||||||
|
|
||||||
|
String tip = null;
|
||||||
|
|
||||||
|
List<Completion> completions = getCompletionsAt(textArea, e.getPoint());
|
||||||
|
if (completions!=null && completions.size()>0) {
|
||||||
|
// Only ever 1 match for us in C...
|
||||||
|
Completion c = completions.get(0);
|
||||||
|
tip = c.getToolTipText();
|
||||||
|
}
|
||||||
|
|
||||||
|
return tip;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
/*
|
||||||
|
* 05/16/2012
|
||||||
|
*
|
||||||
|
* This library is distributed under a modified BSD license. See the included
|
||||||
|
* AutoComplete.License.txt file for details.
|
||||||
|
*/
|
||||||
|
package org.fife.ui.autocomplete;
|
||||||
|
|
||||||
|
import java.net.URL;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Possibly redirects one URL to another. Useful if you want "external" URL's
|
||||||
|
* in code completion documentation to point to a local copy instead, for
|
||||||
|
* example.
|
||||||
|
*
|
||||||
|
* @author Robert Futrell
|
||||||
|
* @version 1.0
|
||||||
|
*/
|
||||||
|
public interface LinkRedirector {
|
||||||
|
|
||||||
|
|
||||||
|
public URL possiblyRedirect(URL original);
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,242 @@
|
|||||||
|
/*
|
||||||
|
* 01/06/2009
|
||||||
|
*
|
||||||
|
* MarkupTagComletion.java - A completion representing a tag in markup, such
|
||||||
|
* as HTML or XML.
|
||||||
|
*
|
||||||
|
* This library is distributed under a modified BSD license. See the included
|
||||||
|
* AutoComplete.License.txt file for details.
|
||||||
|
*/
|
||||||
|
package org.fife.ui.autocomplete;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.fife.ui.autocomplete.ParameterizedCompletion.Parameter;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A completion representing a tag in markup, such as HTML or XML.
|
||||||
|
*
|
||||||
|
* @author Robert Futrell
|
||||||
|
* @version 1.0
|
||||||
|
*/
|
||||||
|
public class MarkupTagCompletion extends AbstractCompletion {
|
||||||
|
|
||||||
|
private String name;
|
||||||
|
private String desc;
|
||||||
|
private String definedIn;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attributes of the tag.
|
||||||
|
*/
|
||||||
|
private List<Parameter> attrs;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
*
|
||||||
|
* @param provider The parent provider instance.
|
||||||
|
* @param name The name of the tag.
|
||||||
|
*/
|
||||||
|
public MarkupTagCompletion(CompletionProvider provider, String name) {
|
||||||
|
super(provider);
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds HTML describing the attributes of this tag to a buffer.
|
||||||
|
*
|
||||||
|
* @param sb The buffer to append to.
|
||||||
|
*/
|
||||||
|
protected void addAttributes(StringBuilder sb) {
|
||||||
|
|
||||||
|
// TODO: Localize me.
|
||||||
|
|
||||||
|
int attrCount = getAttributeCount();
|
||||||
|
if (attrCount>0) {
|
||||||
|
sb.append("<b>Attributes:</b><br>");
|
||||||
|
sb.append("<center><table width='90%'><tr><td>");
|
||||||
|
for (int i=0; i<attrCount; i++) {
|
||||||
|
Parameter attr = getAttribute(i);
|
||||||
|
sb.append(" <b>");
|
||||||
|
sb.append(attr.getName()!=null ? attr.getName() :
|
||||||
|
attr.getType());
|
||||||
|
sb.append("</b> ");
|
||||||
|
String desc = attr.getDescription();
|
||||||
|
if (desc!=null) {
|
||||||
|
sb.append(desc);
|
||||||
|
}
|
||||||
|
sb.append("<br>");
|
||||||
|
}
|
||||||
|
sb.append("</td></tr></table></center><br><br>");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected void addDefinitionString(StringBuilder sb) {
|
||||||
|
sb.append("<html><b>").append(name).append("</b>");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns all attributes of this tag.
|
||||||
|
*
|
||||||
|
* @return A list of {@link ParameterizedCompletion.Parameter}s.
|
||||||
|
* @see #getAttribute(int)
|
||||||
|
* @see #getAttributeCount()
|
||||||
|
*/
|
||||||
|
public List<Parameter> getAttributes() {
|
||||||
|
return attrs;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the specified {@link ParameterizedCompletion.Parameter}.
|
||||||
|
*
|
||||||
|
* @param index The index of the attribute to retrieve.
|
||||||
|
* @return The attribute.
|
||||||
|
* @see #getAttributeCount()
|
||||||
|
*/
|
||||||
|
public Parameter getAttribute(int index) {
|
||||||
|
return attrs.get(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the number of attributes of this tag.
|
||||||
|
*
|
||||||
|
* @return The number of attributes of this tag.
|
||||||
|
* @see #getAttribute(int)
|
||||||
|
*/
|
||||||
|
public int getAttributeCount() {
|
||||||
|
return attrs==null ? 0 : attrs.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns where this variable is defined.
|
||||||
|
*
|
||||||
|
* @return Where this variable is defined.
|
||||||
|
* @see #setDefinedIn(String)
|
||||||
|
*/
|
||||||
|
public String getDefinedIn() {
|
||||||
|
return definedIn;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a short description of this variable. This should be an
|
||||||
|
* HTML snippet.
|
||||||
|
*
|
||||||
|
* @return A short description of this variable. This may be
|
||||||
|
* <code>null</code>.
|
||||||
|
* @see #setDescription(String)
|
||||||
|
*/
|
||||||
|
public String getDescription() {
|
||||||
|
return desc;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the name of this tag.
|
||||||
|
*
|
||||||
|
* @return The name of this tag.
|
||||||
|
*/
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String getReplacementText() {
|
||||||
|
return getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String getSummary() {
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
addDefinitionString(sb);
|
||||||
|
possiblyAddDescription(sb);
|
||||||
|
addAttributes(sb);
|
||||||
|
possiblyAddDefinedIn(sb);
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds some HTML describing where this variable is defined, if this
|
||||||
|
* information is known.
|
||||||
|
*
|
||||||
|
* @param sb The buffer to append to.
|
||||||
|
*/
|
||||||
|
protected void possiblyAddDefinedIn(StringBuilder sb) {
|
||||||
|
if (definedIn!=null) {
|
||||||
|
sb.append("<hr>Defined in:"); // TODO: Localize me
|
||||||
|
sb.append(" <em>").append(definedIn).append("</em>");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds the description text as HTML to a buffer, if a description is
|
||||||
|
* defined.
|
||||||
|
*
|
||||||
|
* @param sb The buffer to append to.
|
||||||
|
*/
|
||||||
|
protected void possiblyAddDescription(StringBuilder sb) {
|
||||||
|
if (desc!=null) {
|
||||||
|
sb.append("<hr><br>");
|
||||||
|
sb.append(desc);
|
||||||
|
sb.append("<br><br><br>");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets where this variable is defined.
|
||||||
|
*
|
||||||
|
* @param definedIn Where this variable is defined.
|
||||||
|
* @see #getDefinedIn()
|
||||||
|
*/
|
||||||
|
public void setDefinedIn(String definedIn) {
|
||||||
|
this.definedIn = definedIn;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the short description of this tag. This should be an
|
||||||
|
* HTML snippet.
|
||||||
|
*
|
||||||
|
* @param desc A short description of this tag. This may be
|
||||||
|
* <code>null</code>.
|
||||||
|
* @see #getDescription()
|
||||||
|
*/
|
||||||
|
public void setDescription(String desc) {
|
||||||
|
this.desc = desc;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the attributes of this tag.
|
||||||
|
*
|
||||||
|
* @param attrs The attributes.
|
||||||
|
* @see #getAttribute(int)
|
||||||
|
* @see #getAttributeCount()
|
||||||
|
*/
|
||||||
|
public void setAttributes(List<? extends Parameter> attrs) {
|
||||||
|
// Deep copy so parsing can re-use its array.
|
||||||
|
this.attrs = new ArrayList<Parameter>(attrs);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,143 @@
|
|||||||
|
/*
|
||||||
|
* 04/26/2009
|
||||||
|
*
|
||||||
|
* OutlineHighlightPainter.java - Highlight painter that draws an outline
|
||||||
|
* around its text.
|
||||||
|
*
|
||||||
|
* This library is distributed under a modified BSD license. See the included
|
||||||
|
* AutoComplete.License.txt file for details.
|
||||||
|
*/
|
||||||
|
package org.fife.ui.autocomplete;
|
||||||
|
|
||||||
|
import java.awt.Color;
|
||||||
|
import java.awt.Graphics;
|
||||||
|
import java.awt.Rectangle;
|
||||||
|
import java.awt.Shape;
|
||||||
|
import javax.swing.text.BadLocationException;
|
||||||
|
import javax.swing.text.DefaultHighlighter;
|
||||||
|
import javax.swing.text.JTextComponent;
|
||||||
|
import javax.swing.text.Position;
|
||||||
|
import javax.swing.text.View;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Highlight painter that draws an outline around the text. This is used to
|
||||||
|
* draw bounds around function/method parameters.
|
||||||
|
*
|
||||||
|
* @author Robert Futrell
|
||||||
|
* @version 1.0
|
||||||
|
*/
|
||||||
|
/*
|
||||||
|
* NOTE: Whenever you see text like "Workaround for Java Highlight issues",
|
||||||
|
* this is because highlighted text in a JTextComponent gets "pushed" forward
|
||||||
|
* when the caret is at the Highlight's start, when we need it to instead get
|
||||||
|
* prepended to. For this reason, the auto-complete package adds its Highlights
|
||||||
|
* 1 char too long (1 char earlier than where it should really start), but only
|
||||||
|
* paint the Highlight from the 2nd char on.
|
||||||
|
*/
|
||||||
|
class OutlineHighlightPainter extends
|
||||||
|
DefaultHighlighter.DefaultHighlightPainter {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DefaultHighlightPainter doesn't allow changing color, so we must cache
|
||||||
|
* ours here.
|
||||||
|
*/
|
||||||
|
private Color color;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
*
|
||||||
|
* @param color The color to draw the bounding boxes with. This cannot
|
||||||
|
* be <code>null</code>.
|
||||||
|
*/
|
||||||
|
public OutlineHighlightPainter(Color color) {
|
||||||
|
super(color);
|
||||||
|
setColor(color);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the color to paint bounding boxes with.
|
||||||
|
*
|
||||||
|
* @return The color.
|
||||||
|
* @see #setColor(Color)
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Color getColor() {
|
||||||
|
return color;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Shape paintLayer(Graphics g, int p0, int p1, Shape viewBounds,
|
||||||
|
JTextComponent c, View view) {
|
||||||
|
|
||||||
|
g.setColor(getColor());
|
||||||
|
p0++; // Workaround for Java Highlight issues.
|
||||||
|
|
||||||
|
// This special case isn't needed for most standard Swing Views (which
|
||||||
|
// always return a width of 1 for modelToView() calls), but it is
|
||||||
|
// needed for RSTA views, which actually return the width of chars for
|
||||||
|
// modelToView calls. But this should be faster anyway, as we
|
||||||
|
// short-circuit and do only one modelToView() for one offset.
|
||||||
|
if (p0==p1) {
|
||||||
|
try {
|
||||||
|
Shape s = view.modelToView(p0, viewBounds,
|
||||||
|
Position.Bias.Forward);
|
||||||
|
Rectangle r = s.getBounds();
|
||||||
|
g.drawLine(r.x, r.y, r.x, r.y+r.height);
|
||||||
|
return r;
|
||||||
|
} catch (BadLocationException ble) {
|
||||||
|
ble.printStackTrace(); // Never happens
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (p0 == view.getStartOffset() && p1 == view.getEndOffset()) {
|
||||||
|
// Contained in view, can just use bounds.
|
||||||
|
Rectangle alloc;
|
||||||
|
if (viewBounds instanceof Rectangle) {
|
||||||
|
alloc = (Rectangle) viewBounds;
|
||||||
|
} else {
|
||||||
|
alloc = viewBounds.getBounds();
|
||||||
|
}
|
||||||
|
g.drawRect(alloc.x, alloc.y, alloc.width - 1, alloc.height - 1);
|
||||||
|
return alloc;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Should only render part of View.
|
||||||
|
try {
|
||||||
|
// --- determine locations ---
|
||||||
|
Shape shape = view.modelToView(p0, Position.Bias.Forward, p1,
|
||||||
|
Position.Bias.Backward, viewBounds);
|
||||||
|
Rectangle r = (shape instanceof Rectangle) ? (Rectangle) shape
|
||||||
|
: shape.getBounds();
|
||||||
|
g.drawRect(r.x, r.y, r.width - 1, r.height - 1);
|
||||||
|
return r;
|
||||||
|
} catch (BadLocationException e) { // Never happens
|
||||||
|
e.printStackTrace();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the color to paint the bounding boxes with.
|
||||||
|
*
|
||||||
|
* @param color The new color. This cannot be <code>null</code>.
|
||||||
|
* @see #getColor()
|
||||||
|
*/
|
||||||
|
public void setColor(Color color) {
|
||||||
|
if (color==null) {
|
||||||
|
throw new IllegalArgumentException("color cannot be null");
|
||||||
|
}
|
||||||
|
this.color = color;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
/*
|
||||||
|
* 12/14/2010
|
||||||
|
*
|
||||||
|
* ParameterChoicesProvider.java - Provides completions for a
|
||||||
|
* ParameterizedCompletion's parameters.
|
||||||
|
*
|
||||||
|
* This library is distributed under a modified BSD license. See the included
|
||||||
|
* AutoComplete.License.txt file for details.
|
||||||
|
*/
|
||||||
|
package org.fife.ui.autocomplete;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import javax.swing.text.JTextComponent;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides completions for a {@link ParameterizedCompletion}'s parameters.
|
||||||
|
* So, for example, if the user code-completes a function or method, if
|
||||||
|
* a <code>ParameterChoicesProvider</code> is installed, it can return possible
|
||||||
|
* completions for the parameters to that function or method.
|
||||||
|
*
|
||||||
|
* @author Robert Futrell
|
||||||
|
* @version 1.0
|
||||||
|
*/
|
||||||
|
public interface ParameterChoicesProvider {
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a list of choices for a specific parameter.
|
||||||
|
*
|
||||||
|
* @param tc The text component.
|
||||||
|
* @param param The currently focused parameter.
|
||||||
|
* @return The list of parameters. This may be <code>null</code> for
|
||||||
|
* "no parameters," but might also be an empty list.
|
||||||
|
*/
|
||||||
|
public List<Completion> getParameterChoices(JTextComponent tc,
|
||||||
|
ParameterizedCompletion.Parameter param);
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,172 @@
|
|||||||
|
/*
|
||||||
|
* 12/11/2010
|
||||||
|
*
|
||||||
|
* ParameterizedCompletion.java - A completion option.
|
||||||
|
*
|
||||||
|
* This library is distributed under a modified BSD license. See the included
|
||||||
|
* AutoComplete.License.txt file for details.
|
||||||
|
*/
|
||||||
|
package org.fife.ui.autocomplete;
|
||||||
|
|
||||||
|
import javax.swing.text.JTextComponent;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A completion option that takes parameters, such as a function or method.
|
||||||
|
*
|
||||||
|
* @author Robert Futrell
|
||||||
|
* @version 1.0
|
||||||
|
*/
|
||||||
|
public interface ParameterizedCompletion extends Completion {
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the "definition string" for this completion. For example,
|
||||||
|
* for the C "<code>printf</code>" function, this would return
|
||||||
|
* "<code>int printf(const char *, ...)</code>".
|
||||||
|
*
|
||||||
|
* @return The definition string.
|
||||||
|
*/
|
||||||
|
public String getDefinitionString();
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the specified {@link Parameter}.
|
||||||
|
*
|
||||||
|
* @param index The index of the parameter to retrieve.
|
||||||
|
* @return The parameter.
|
||||||
|
* @see #getParamCount()
|
||||||
|
*/
|
||||||
|
public Parameter getParam(int index);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the number of parameters this completion takes.
|
||||||
|
*
|
||||||
|
* @return The number of parameters this completion takes.
|
||||||
|
* @see #getParam(int)
|
||||||
|
*/
|
||||||
|
public int getParamCount();
|
||||||
|
|
||||||
|
|
||||||
|
public ParameterizedCompletionInsertionInfo getInsertionInfo(
|
||||||
|
JTextComponent tc, boolean replaceTabsWithSpaces);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether a tool tip displaying assistance for each parameter
|
||||||
|
* while it is being edited is appropriate for this completion.
|
||||||
|
*
|
||||||
|
* @return Whether the tool tip is appropriate to display.
|
||||||
|
*/
|
||||||
|
public boolean getShowParameterToolTip();
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A parameter passed to a parameterized {@link Completion}.
|
||||||
|
*/
|
||||||
|
public static class Parameter {
|
||||||
|
|
||||||
|
private String name;
|
||||||
|
private Object type;
|
||||||
|
private String desc;
|
||||||
|
private boolean isEndParam;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
*
|
||||||
|
* @param type The type of this parameter. This may be
|
||||||
|
* <code>null</code> for languages without specific types,
|
||||||
|
* dynamic typing, etc. Usually you'll pass a String for this
|
||||||
|
* value, but you may pass any object representing a type in
|
||||||
|
* your language, as long as its <code>toString()</code> method
|
||||||
|
* returns a string representation of the type.
|
||||||
|
* @param name The name of the parameter.
|
||||||
|
*/
|
||||||
|
public Parameter(Object type, String name) {
|
||||||
|
this(type, name, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
*
|
||||||
|
* @param type The type of this parameter. This may be
|
||||||
|
* <code>null</code> for languages without specific types,
|
||||||
|
* dynamic typing, etc. Usually you'll pass a String for this
|
||||||
|
* value, but you may pass any object representing a type in
|
||||||
|
* your language, as long as its <code>toString()</code> method
|
||||||
|
* returns a string representation of the type.
|
||||||
|
* @param name The name of the parameter.
|
||||||
|
* @param endParam Whether this parameter is an "ending parameter;"
|
||||||
|
* that is, whether this parameter is at a logical "ending
|
||||||
|
* point" in the completion text. If the user types in a
|
||||||
|
* parameter that is an ending point, parameter completion mode
|
||||||
|
* terminates. Set this to <code>true</code> for a trailing
|
||||||
|
* parameter after a function call's closing ')', for example.
|
||||||
|
*/
|
||||||
|
public Parameter(Object type, String name, boolean endParam) {
|
||||||
|
this.name = name;
|
||||||
|
this.type = type;
|
||||||
|
this.isEndParam = endParam;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDescription() {
|
||||||
|
return desc;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the type of this parameter, as a string.
|
||||||
|
*
|
||||||
|
* @return The type of the parameter, or <code>null</code> for none.
|
||||||
|
*/
|
||||||
|
public String getType() {
|
||||||
|
return type==null ? null : type.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the object used to describe the type of this parameter.
|
||||||
|
*
|
||||||
|
* @return The type object, or <code>null</code> for none.
|
||||||
|
*/
|
||||||
|
public Object getTypeObject() {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Whether this parameter is an "ending parameter;"
|
||||||
|
* that is, whether this parameter is at a logical "ending
|
||||||
|
* point" in the completion text. If the user types in a
|
||||||
|
* parameter that is an ending point, parameter completion mode
|
||||||
|
* terminates.
|
||||||
|
*/
|
||||||
|
public boolean isEndParam() {
|
||||||
|
return isEndParam;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDescription(String desc) {
|
||||||
|
this.desc = desc;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
if (getType()!=null) {
|
||||||
|
sb.append(getType());
|
||||||
|
}
|
||||||
|
if (getName()!=null) {
|
||||||
|
if (getType()!=null) {
|
||||||
|
sb.append(' ');
|
||||||
|
}
|
||||||
|
sb.append(getName());
|
||||||
|
}
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,314 @@
|
|||||||
|
/*
|
||||||
|
* 12/11/2010
|
||||||
|
*
|
||||||
|
* ParameterizedCompletionChoicesWindow.java - A list of likely choices for a
|
||||||
|
* parameter.
|
||||||
|
*
|
||||||
|
* This library is distributed under a modified BSD license. See the included
|
||||||
|
* AutoComplete.License.txt file for details.
|
||||||
|
*/
|
||||||
|
package org.fife.ui.autocomplete;
|
||||||
|
|
||||||
|
import java.awt.ComponentOrientation;
|
||||||
|
import java.awt.Dimension;
|
||||||
|
import java.awt.Rectangle;
|
||||||
|
import java.awt.Window;
|
||||||
|
import java.awt.event.MouseAdapter;
|
||||||
|
import java.awt.event.MouseEvent;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.List;
|
||||||
|
import javax.swing.DefaultListModel;
|
||||||
|
import javax.swing.JList;
|
||||||
|
import javax.swing.JScrollPane;
|
||||||
|
import javax.swing.JWindow;
|
||||||
|
import javax.swing.SwingUtilities;
|
||||||
|
import javax.swing.text.JTextComponent;
|
||||||
|
|
||||||
|
import org.fife.ui.rsyntaxtextarea.PopupWindowDecorator;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A small popup window offering a list of likely choices for a parameter
|
||||||
|
* when the user has code-completed a parameterized completion. For example,
|
||||||
|
* if they have just code-completed the C function "<code>fprintf</code>",
|
||||||
|
* when entering the file name, this popup might display all local variables
|
||||||
|
* of type "<code>char *</code>".
|
||||||
|
*
|
||||||
|
* @author Robert Futrell
|
||||||
|
* @version 1.0
|
||||||
|
*/
|
||||||
|
public class ParameterizedCompletionChoicesWindow extends JWindow {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The parent AutoCompletion instance.
|
||||||
|
*/
|
||||||
|
private AutoCompletion ac;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The list of completion choices.
|
||||||
|
*/
|
||||||
|
private JList list;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The currently displayed completion choices.
|
||||||
|
*/
|
||||||
|
private DefaultListModel model;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A list of lists of choices for each parameter.
|
||||||
|
*/
|
||||||
|
private List<List<Completion>> choicesListList;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The scroll pane containing the list.
|
||||||
|
*/
|
||||||
|
private JScrollPane sp;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Comparator used to sort completions by their relevance before sorting
|
||||||
|
* them lexicographically.
|
||||||
|
*/
|
||||||
|
private static final Comparator<Completion> sortByRelevanceComparator =
|
||||||
|
new SortByRelevanceComparator();
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
*
|
||||||
|
* @param parent The parent window (hosting the text component).
|
||||||
|
* @param ac The auto-completion instance.
|
||||||
|
* @param context The completion context.
|
||||||
|
*/
|
||||||
|
public ParameterizedCompletionChoicesWindow(Window parent,
|
||||||
|
AutoCompletion ac,
|
||||||
|
final ParameterizedCompletionContext context) {
|
||||||
|
|
||||||
|
super(parent);
|
||||||
|
this.ac = ac;
|
||||||
|
ComponentOrientation o = ac.getTextComponentOrientation();
|
||||||
|
|
||||||
|
model = new DefaultListModel();
|
||||||
|
list = new JList(model);
|
||||||
|
if (ac.getParamChoicesRenderer()!=null) {
|
||||||
|
list.setCellRenderer(ac.getParamChoicesRenderer());
|
||||||
|
}
|
||||||
|
list.addMouseListener(new MouseAdapter() {
|
||||||
|
@Override
|
||||||
|
public void mouseClicked(MouseEvent e) {
|
||||||
|
if (e.getClickCount()==2) {
|
||||||
|
context.insertSelectedChoice();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
sp = new JScrollPane(list);
|
||||||
|
|
||||||
|
setContentPane(sp);
|
||||||
|
applyComponentOrientation(o);
|
||||||
|
setFocusableWindowState(false);
|
||||||
|
|
||||||
|
// Give apps a chance to decorate us with drop shadows, etc.
|
||||||
|
PopupWindowDecorator decorator = PopupWindowDecorator.get();
|
||||||
|
if (decorator!=null) {
|
||||||
|
decorator.decorate(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the selected value.
|
||||||
|
*
|
||||||
|
* @return The selected value, or <code>null</code> if nothing is
|
||||||
|
* selected.
|
||||||
|
*/
|
||||||
|
public String getSelectedChoice() {
|
||||||
|
Completion c = (Completion)list.getSelectedValue();
|
||||||
|
return c==null ? null : c.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Changes the selected index.
|
||||||
|
*
|
||||||
|
* @param amount The amount by which to change the selected index.
|
||||||
|
*/
|
||||||
|
public void incSelection(int amount) {
|
||||||
|
int selection = list.getSelectedIndex();
|
||||||
|
selection += amount;
|
||||||
|
if (selection<0) {
|
||||||
|
// Account for nothing selected yet
|
||||||
|
selection = model.getSize()-1;//+= model.getSize();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
selection %= model.getSize();
|
||||||
|
}
|
||||||
|
list.setSelectedIndex(selection);
|
||||||
|
list.ensureIndexIsVisible(selection);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes this window to offer suggestions for the parameters of
|
||||||
|
* a specific completion.
|
||||||
|
*
|
||||||
|
* @param pc The completion whose parameters we should offer suggestions
|
||||||
|
* for.
|
||||||
|
*/
|
||||||
|
public void initialize(ParameterizedCompletion pc) {
|
||||||
|
|
||||||
|
CompletionProvider provider = pc.getProvider();
|
||||||
|
ParameterChoicesProvider pcp = provider.getParameterChoicesProvider();
|
||||||
|
if (pcp==null) {
|
||||||
|
choicesListList = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int paramCount = pc.getParamCount();
|
||||||
|
choicesListList = new ArrayList<List<Completion>>(paramCount);
|
||||||
|
JTextComponent tc = ac.getTextComponent();
|
||||||
|
|
||||||
|
for (int i=0; i<paramCount; i++) {
|
||||||
|
ParameterizedCompletion.Parameter param = pc.getParam(i);
|
||||||
|
List<Completion> choices = pcp.getParameterChoices(tc, param);
|
||||||
|
choicesListList.add(choices);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the location of this window relative to the given rectangle.
|
||||||
|
*
|
||||||
|
* @param r The visual position of the caret (in screen coordinates).
|
||||||
|
*/
|
||||||
|
public void setLocationRelativeTo(Rectangle r) {
|
||||||
|
|
||||||
|
// Multi-monitor support - make sure the completion window (and
|
||||||
|
// description window, if applicable) both fit in the same window in
|
||||||
|
// a multi-monitor environment. To do this, we decide which monitor
|
||||||
|
// the rectangle "r" is in, and use that one (just pick top-left corner
|
||||||
|
// as the defining point).
|
||||||
|
Rectangle screenBounds = Util.getScreenBoundsForPoint(r.x, r.y);
|
||||||
|
//Dimension screenSize = tooltip.getToolkit().getScreenSize();
|
||||||
|
|
||||||
|
// Try putting our stuff "below" the caret first.
|
||||||
|
int y = r.y + r.height + 5;
|
||||||
|
|
||||||
|
// Get x-coordinate of completions. Try to align left edge with the
|
||||||
|
// caret first.
|
||||||
|
int x = r.x;
|
||||||
|
if (x<screenBounds.x) {
|
||||||
|
x = screenBounds.x;
|
||||||
|
}
|
||||||
|
else if (x+getWidth()>screenBounds.x+screenBounds.width) { // completions don't fit
|
||||||
|
x = screenBounds.x + screenBounds.width - getWidth();
|
||||||
|
}
|
||||||
|
|
||||||
|
setLocation(x, y);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Displays the choices for the specified parameter matching the given
|
||||||
|
* text. This will display or hide this popup window as necessary.
|
||||||
|
*
|
||||||
|
* @param param The index of the parameter the caret is currently in.
|
||||||
|
* This may be <code>-1</code> if not in a parameter (i.e., on
|
||||||
|
* the comma between parameters).
|
||||||
|
* @param prefix Text in the parameter before the dot. This may
|
||||||
|
* be <code>null</code> to represent the empty string.
|
||||||
|
*/
|
||||||
|
public void setParameter(int param, String prefix) {
|
||||||
|
|
||||||
|
model.clear();
|
||||||
|
List<Completion> temp = new ArrayList<Completion>();
|
||||||
|
|
||||||
|
if (choicesListList!=null && param>=0 && param<choicesListList.size()) {
|
||||||
|
|
||||||
|
List<Completion> choices = choicesListList.get(param);
|
||||||
|
if (choices!=null) {
|
||||||
|
for (Completion c : choices) {
|
||||||
|
String choice = c.getReplacementText();
|
||||||
|
if (prefix==null || Util.startsWithIgnoreCase(choice, prefix)) {
|
||||||
|
temp.add(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort completions appropriately.
|
||||||
|
Comparator<Completion> c = null;
|
||||||
|
if (/*sortByRelevance*/true) {
|
||||||
|
c = sortByRelevanceComparator;
|
||||||
|
}
|
||||||
|
Collections.sort(temp, c);
|
||||||
|
for (int i=0; i<temp.size(); i++) {
|
||||||
|
model.addElement(temp.get(i));
|
||||||
|
}
|
||||||
|
|
||||||
|
int visibleRowCount = Math.min(model.size(), 10);
|
||||||
|
list.setVisibleRowCount(visibleRowCount);
|
||||||
|
|
||||||
|
// Toggle visibility, if necessary.
|
||||||
|
if (visibleRowCount==0 && isVisible()) {
|
||||||
|
setVisible(false);
|
||||||
|
}
|
||||||
|
else if (visibleRowCount>0) {
|
||||||
|
Dimension size = getPreferredSize();
|
||||||
|
if (size.width<150) {
|
||||||
|
setSize(150, size.height);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
pack();
|
||||||
|
}
|
||||||
|
// Make sure nothing is ever obscured by vertical scroll bar.
|
||||||
|
if (sp.getVerticalScrollBar()!=null &&
|
||||||
|
sp.getVerticalScrollBar().isVisible()) {
|
||||||
|
size = getSize();
|
||||||
|
int w = size.width + sp.getVerticalScrollBar().getWidth()+5;
|
||||||
|
setSize(w, size.height);
|
||||||
|
}
|
||||||
|
list.setSelectedIndex(0);
|
||||||
|
list.ensureIndexIsVisible(0);
|
||||||
|
if (!isVisible()) {
|
||||||
|
setVisible(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
else {
|
||||||
|
setVisible(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Toggles the visibility of this popup window.
|
||||||
|
*
|
||||||
|
* @param visible Whether this window should be visible.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void setVisible(boolean visible) {
|
||||||
|
if (visible!=isVisible()) {
|
||||||
|
// i.e. if no possibilities matched what's been typed
|
||||||
|
if (visible && model.size()==0) {//list.getVisibleRowCount()==0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
super.setVisible(visible);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the <tt>LookAndFeel</tt> of this window.
|
||||||
|
*/
|
||||||
|
public void updateUI() {
|
||||||
|
SwingUtilities.updateComponentTreeUI(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,211 @@
|
|||||||
|
/*
|
||||||
|
* 12/21/2008
|
||||||
|
*
|
||||||
|
* ParameterizedCompletionDescriptionToolTip.java - A "tool tip" displaying
|
||||||
|
* information on the function or method currently being entered.
|
||||||
|
*
|
||||||
|
* This library is distributed under a modified BSD license. See the included
|
||||||
|
* AutoComplete.License.txt file for details.
|
||||||
|
*/
|
||||||
|
package org.fife.ui.autocomplete;
|
||||||
|
|
||||||
|
import java.awt.BorderLayout;
|
||||||
|
import java.awt.Rectangle;
|
||||||
|
import java.awt.Window;
|
||||||
|
import javax.swing.BorderFactory;
|
||||||
|
import javax.swing.JLabel;
|
||||||
|
import javax.swing.JPanel;
|
||||||
|
import javax.swing.JWindow;
|
||||||
|
import javax.swing.SwingUtilities;
|
||||||
|
|
||||||
|
import org.fife.ui.rsyntaxtextarea.PopupWindowDecorator;
|
||||||
|
import org.fife.ui.rsyntaxtextarea.RSyntaxUtilities;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A "tool tip" that displays information on the function or method currently
|
||||||
|
* being entered.
|
||||||
|
*
|
||||||
|
* @author Robert Futrell
|
||||||
|
* @version 1.0
|
||||||
|
*/
|
||||||
|
class ParameterizedCompletionDescriptionToolTip {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The actual tool tip.
|
||||||
|
*/
|
||||||
|
private JWindow tooltip;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The label that holds the description.
|
||||||
|
*/
|
||||||
|
private JLabel descLabel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The completion being described.
|
||||||
|
*/
|
||||||
|
private ParameterizedCompletion pc;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
*
|
||||||
|
* @param owner The parent window.
|
||||||
|
* @param ac The parent auto-completion.
|
||||||
|
* @param pc The completion being described.
|
||||||
|
*/
|
||||||
|
public ParameterizedCompletionDescriptionToolTip(Window owner,
|
||||||
|
ParameterizedCompletionContext context,
|
||||||
|
AutoCompletion ac, ParameterizedCompletion pc) {
|
||||||
|
|
||||||
|
tooltip = new JWindow(owner);
|
||||||
|
|
||||||
|
this.pc = pc;
|
||||||
|
|
||||||
|
descLabel = new JLabel();
|
||||||
|
descLabel.setBorder(BorderFactory.createCompoundBorder(
|
||||||
|
TipUtil.getToolTipBorder(),
|
||||||
|
BorderFactory.createEmptyBorder(2, 5, 2, 5)));
|
||||||
|
descLabel.setOpaque(true);
|
||||||
|
descLabel.setBackground(TipUtil.getToolTipBackground());
|
||||||
|
// It appears that if a JLabel is set as a content pane directly, when
|
||||||
|
// using the JDK's opacity API's, it won't paint its background, even
|
||||||
|
// if label.setOpaque(true) is called. You have to have a container
|
||||||
|
// underneath it for it to paint its background. Thus, we embed our
|
||||||
|
// label in a parent JPanel to handle this case.
|
||||||
|
//tooltip.setContentPane(descLabel);
|
||||||
|
JPanel panel = new JPanel(new BorderLayout());
|
||||||
|
panel.add(descLabel);
|
||||||
|
tooltip.setContentPane(panel);
|
||||||
|
|
||||||
|
// Give apps a chance to decorate us with drop shadows, etc.
|
||||||
|
PopupWindowDecorator decorator = PopupWindowDecorator.get();
|
||||||
|
if (decorator!=null) {
|
||||||
|
decorator.decorate(tooltip);
|
||||||
|
}
|
||||||
|
|
||||||
|
updateText(0);
|
||||||
|
|
||||||
|
tooltip.setFocusableWindowState(false);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether this tool tip is visible.
|
||||||
|
*
|
||||||
|
* @return Whether this tool tip is visible.
|
||||||
|
* @see #setVisible(boolean)
|
||||||
|
*/
|
||||||
|
public boolean isVisible() {
|
||||||
|
return tooltip.isVisible();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the location of this tool tip relative to the given rectangle.
|
||||||
|
*
|
||||||
|
* @param r The visual position of the caret (in screen coordinates).
|
||||||
|
*/
|
||||||
|
public void setLocationRelativeTo(Rectangle r) {
|
||||||
|
|
||||||
|
// Multi-monitor support - make sure the completion window (and
|
||||||
|
// description window, if applicable) both fit in the same window in
|
||||||
|
// a multi-monitor environment. To do this, we decide which monitor
|
||||||
|
// the rectangle "r" is in, and use that one (just pick top-left corner
|
||||||
|
// as the defining point).
|
||||||
|
Rectangle screenBounds = Util.getScreenBoundsForPoint(r.x, r.y);
|
||||||
|
//Dimension screenSize = tooltip.getToolkit().getScreenSize();
|
||||||
|
|
||||||
|
// Try putting our stuff "above" the caret first.
|
||||||
|
int y = r.y - 5 - tooltip.getHeight();
|
||||||
|
if (y<0) {
|
||||||
|
y = r.y + r.height + 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get x-coordinate of completions. Try to align left edge with the
|
||||||
|
// caret first.
|
||||||
|
int x = r.x;
|
||||||
|
if (x<screenBounds.x) {
|
||||||
|
x = screenBounds.x;
|
||||||
|
}
|
||||||
|
else if (x+tooltip.getWidth()>screenBounds.x+screenBounds.width) { // completions don't fit
|
||||||
|
x = screenBounds.x + screenBounds.width - tooltip.getWidth();
|
||||||
|
}
|
||||||
|
|
||||||
|
tooltip.setLocation(x, y);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Toggles the visibility of this tool tip.
|
||||||
|
*
|
||||||
|
* @param visible Whether this tool tip should be visible.
|
||||||
|
* @see #isVisible()
|
||||||
|
*/
|
||||||
|
public void setVisible(boolean visible) {
|
||||||
|
tooltip.setVisible(visible);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the text in the tool tip to have the current parameter
|
||||||
|
* displayed in bold.
|
||||||
|
*
|
||||||
|
* @param selectedParam The index of the selected parameter.
|
||||||
|
* @return Whether the text needed to be updated.
|
||||||
|
*/
|
||||||
|
public boolean updateText(int selectedParam) {
|
||||||
|
|
||||||
|
StringBuilder sb = new StringBuilder("<html>");
|
||||||
|
int paramCount = pc.getParamCount();
|
||||||
|
for (int i=0; i<paramCount; i++) {
|
||||||
|
|
||||||
|
if (i==selectedParam) {
|
||||||
|
sb.append("<b>");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Some parameter types may have chars in them unfriendly to HTML
|
||||||
|
// (such as type parameters in Java). We need to take care to
|
||||||
|
// escape these.
|
||||||
|
String temp = pc.getParam(i).toString();
|
||||||
|
sb.append(RSyntaxUtilities.escapeForHtml(temp, "<br>", false));
|
||||||
|
|
||||||
|
if (i==selectedParam) {
|
||||||
|
sb.append("</b>");
|
||||||
|
}
|
||||||
|
if (i<paramCount-1) {
|
||||||
|
sb.append(pc.getProvider().getParameterListSeparator());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selectedParam>=0 && selectedParam<paramCount) {
|
||||||
|
ParameterizedCompletion.Parameter param =
|
||||||
|
pc.getParam(selectedParam);
|
||||||
|
String desc = param.getDescription();
|
||||||
|
if (desc!=null) {
|
||||||
|
sb.append("<br>");
|
||||||
|
sb.append(desc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
descLabel.setText(sb.toString());
|
||||||
|
tooltip.pack();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the <tt>LookAndFeel</tt> of this window and the description
|
||||||
|
* window.
|
||||||
|
*/
|
||||||
|
public void updateUI() {
|
||||||
|
SwingUtilities.updateComponentTreeUI(tooltip);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,251 @@
|
|||||||
|
/*
|
||||||
|
* 05/26/2012
|
||||||
|
*
|
||||||
|
* ParameterizedCompletionInsertionInfo.java - Used internally to track the
|
||||||
|
* changes required for a specific parameterized completion.
|
||||||
|
*
|
||||||
|
* This library is distributed under a modified BSD license. See the included
|
||||||
|
* AutoComplete.License.txt file for details.
|
||||||
|
*/
|
||||||
|
package org.fife.ui.autocomplete;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import javax.swing.text.Position;
|
||||||
|
|
||||||
|
import org.fife.ui.rsyntaxtextarea.DocumentRange;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Describes a parameterized completion - what's being inserted, where the
|
||||||
|
* parameters are in the inserted text, etc.
|
||||||
|
*
|
||||||
|
* @author Robert Futrell
|
||||||
|
* @version 1.0
|
||||||
|
*/
|
||||||
|
class ParameterizedCompletionInsertionInfo {
|
||||||
|
|
||||||
|
private int minOffs;
|
||||||
|
private Position maxOffs;
|
||||||
|
private int defaultEnd;
|
||||||
|
private int selStart;
|
||||||
|
private int selEnd;
|
||||||
|
private String textToInsert;
|
||||||
|
private List<DocumentRange> replacementLocations;
|
||||||
|
private List<ReplacementCopy> replacementCopies;
|
||||||
|
|
||||||
|
|
||||||
|
public ParameterizedCompletionInsertionInfo() {
|
||||||
|
defaultEnd = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void addReplacementCopy(String id, int start, int end) {
|
||||||
|
if (replacementCopies==null) {
|
||||||
|
replacementCopies = new ArrayList<ReplacementCopy>(1);
|
||||||
|
}
|
||||||
|
replacementCopies.add(new ReplacementCopy(id, start, end));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Marks a region of the replacement text as representing a variable name
|
||||||
|
* or some other construct that the user should replace.
|
||||||
|
*
|
||||||
|
* @param start The start offset.
|
||||||
|
* @param end The end offset.
|
||||||
|
* @see #getReplacementCount()
|
||||||
|
* @see #getReplacementLocation(int)
|
||||||
|
*/
|
||||||
|
public void addReplacementLocation(int start, int end) {
|
||||||
|
if (replacementLocations==null) {
|
||||||
|
replacementLocations = new ArrayList<DocumentRange>(1);
|
||||||
|
}
|
||||||
|
replacementLocations.add(new DocumentRange(start, end));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public int getDefaultEndOffs() {
|
||||||
|
return defaultEnd>-1 ? defaultEnd : getMaxOffset().getOffset();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the maximum offset the caret can move to before being outside
|
||||||
|
* of the text inserted for this completion.
|
||||||
|
*
|
||||||
|
* @return The maximum offset.
|
||||||
|
* @see #getMinOffset()
|
||||||
|
*/
|
||||||
|
public Position getMaxOffset() {
|
||||||
|
return maxOffs;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the minimum offset the caret can move to before being outside
|
||||||
|
* of the text inserted for this completion.
|
||||||
|
*
|
||||||
|
* @return The minimum offset.
|
||||||
|
* @see #getMaxOffset()
|
||||||
|
*/
|
||||||
|
public int getMinOffset() {
|
||||||
|
return minOffs;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public int getReplacementCopyCount() {
|
||||||
|
return replacementCopies==null ? 0 : replacementCopies.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the number of replacements in the completion.
|
||||||
|
*
|
||||||
|
* @return The number of replacements in the completion.
|
||||||
|
*/
|
||||||
|
public int getReplacementCount() {
|
||||||
|
return replacementLocations==null ? 0 : replacementLocations.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public ReplacementCopy getReplacementCopy(int index) {
|
||||||
|
return replacementCopies.get(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the starting- and ending-offsets of the replacement regions
|
||||||
|
* in the completion.
|
||||||
|
*
|
||||||
|
* @param index The replacement region.
|
||||||
|
* @return The range in the document of that replacement region.
|
||||||
|
* @see #getReplacementCount()
|
||||||
|
*/
|
||||||
|
public DocumentRange getReplacementLocation(int index) {
|
||||||
|
return replacementLocations.get(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the offset that should be the end of the initially selected
|
||||||
|
* text when the completion is inserted (i.e., the end offset of the first
|
||||||
|
* replacement region).
|
||||||
|
*
|
||||||
|
* @return The end offset for the initial selection.
|
||||||
|
* @see #getSelectionStart()
|
||||||
|
*/
|
||||||
|
public int getSelectionEnd() {
|
||||||
|
return selEnd;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the offset that should be the start of the initially selected
|
||||||
|
* text when the completion is inserted (i.e., the start offset of the
|
||||||
|
* first replacement region).
|
||||||
|
*
|
||||||
|
* @return The start offset for the initial selection.
|
||||||
|
* @see #getSelectionEnd()
|
||||||
|
*/
|
||||||
|
public int getSelectionStart() {
|
||||||
|
return selStart;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the actual text to insert when the completion is selected.
|
||||||
|
*
|
||||||
|
* @return The text to insert.
|
||||||
|
* @see #setTextToInsert(String)
|
||||||
|
*/
|
||||||
|
public String getTextToInsert() {
|
||||||
|
return textToInsert;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether or not there is an initial selected region for the
|
||||||
|
* completion (i.e., whether the completion actually has any parameters).
|
||||||
|
*
|
||||||
|
* @return Whether there is a region to initially select for the completion.
|
||||||
|
*/
|
||||||
|
public boolean hasSelection() {
|
||||||
|
return selEnd!=selStart;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the initially selected region for the completion.
|
||||||
|
*
|
||||||
|
* @param selStart The selection start.
|
||||||
|
* @param selEnd The selection end.
|
||||||
|
* @see #getSelectionEnd()
|
||||||
|
* @see #getSelectionStart()
|
||||||
|
*/
|
||||||
|
public void setInitialSelection(int selStart, int selEnd) {
|
||||||
|
this.selStart = selStart;
|
||||||
|
this.selEnd = selEnd;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the document range the caret can move around in before being
|
||||||
|
* outside of the text inserted for the completion.
|
||||||
|
*
|
||||||
|
* @param minOffs The minimum offset.
|
||||||
|
* @param maxOffs The maximum offset, that will track its location as the
|
||||||
|
* document is modified.
|
||||||
|
* @see #getMinOffset()
|
||||||
|
* @see #getMaxOffset()
|
||||||
|
*/
|
||||||
|
public void setCaretRange(int minOffs, Position maxOffs) {
|
||||||
|
this.minOffs = minOffs;
|
||||||
|
this.maxOffs = maxOffs;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void setDefaultEndOffs(int end) {
|
||||||
|
defaultEnd = end;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the text to insert for the completion.
|
||||||
|
*
|
||||||
|
* @param text The text to insert.
|
||||||
|
* @see #getTextToInsert()
|
||||||
|
*/
|
||||||
|
public void setTextToInsert(String text) {
|
||||||
|
this.textToInsert = text;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static class ReplacementCopy {
|
||||||
|
|
||||||
|
private String id;
|
||||||
|
private int start;
|
||||||
|
private int end;
|
||||||
|
|
||||||
|
public ReplacementCopy(String id, int start, int end) {
|
||||||
|
this.id = id;
|
||||||
|
this.start = start;
|
||||||
|
this.end = end;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getEnd() {
|
||||||
|
return end;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getStart() {
|
||||||
|
return start;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,154 @@
|
|||||||
|
/*
|
||||||
|
* 12/02/2013
|
||||||
|
*
|
||||||
|
* This library is distributed under a modified BSD license. See the included
|
||||||
|
* AutoComplete.License.txt file for details.
|
||||||
|
*/
|
||||||
|
package org.fife.ui.autocomplete;
|
||||||
|
|
||||||
|
import java.awt.event.ActionEvent;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import javax.swing.Action;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An <code>AutoCompletion</code> that adds the ability to cycle through a set
|
||||||
|
* of <code>CompletionProvider</code>s via the trigger key. This allows the
|
||||||
|
* application to logically "group together" completions of similar kinds;
|
||||||
|
* for example, Java code completions vs. template completions.<p>
|
||||||
|
*
|
||||||
|
* Usage:
|
||||||
|
* <pre>
|
||||||
|
* XPathDynamicCompletionProvider dynamicProvider = new XPathDynamicCompletionProvider();
|
||||||
|
* RoundRobinAutoCompletion ac = new RoundRobinAutoCompletion(dynamicProvider);
|
||||||
|
* XPathCompletionProvider staticProvider = new XPathCompletionProvider();
|
||||||
|
* ac.addCompletionProvider(staticProvider);
|
||||||
|
* ac.setXXX(..);
|
||||||
|
* ...
|
||||||
|
* ac.install(textArea);
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* @author mschlegel
|
||||||
|
*/
|
||||||
|
public class RoundRobinAutoCompletion extends AutoCompletion {
|
||||||
|
|
||||||
|
/** The List of CompletionProviders to use */
|
||||||
|
private List<CompletionProvider> cycle = new ArrayList<CompletionProvider>();
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
*
|
||||||
|
* @param provider A single completion provider.
|
||||||
|
* @see #addCompletionProvider(CompletionProvider)
|
||||||
|
*/
|
||||||
|
public RoundRobinAutoCompletion(CompletionProvider provider) {
|
||||||
|
|
||||||
|
super(provider);
|
||||||
|
cycle.add(provider);
|
||||||
|
|
||||||
|
// principal requirement for round-robin
|
||||||
|
setHideOnCompletionProviderChange(false);
|
||||||
|
// this is required since otherwise, on empty list of completions for
|
||||||
|
// one of the CompletionProviders, round-robin completion would not
|
||||||
|
// work
|
||||||
|
setHideOnNoText(false);
|
||||||
|
// this is required to prevent single choice of 1st provider to choose
|
||||||
|
// the completion since the user may want the second provider to be
|
||||||
|
// chosen.
|
||||||
|
setAutoCompleteSingleChoices(false);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds an additional <code>CompletionProvider</code> to the list to
|
||||||
|
* cycle through.
|
||||||
|
*
|
||||||
|
* @param provider The new completion provider.
|
||||||
|
*/
|
||||||
|
public void addCompletionProvider(CompletionProvider provider) {
|
||||||
|
cycle.add(provider);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Moves to the next Provider internally. Needs refresh of the popup window
|
||||||
|
* to display the changes.
|
||||||
|
*
|
||||||
|
* @return true if the next provider was the default one (thus returned to
|
||||||
|
* the default view). May be used in case you like to hide the
|
||||||
|
* popup in this case.
|
||||||
|
*/
|
||||||
|
public boolean advanceProvider() {
|
||||||
|
CompletionProvider currentProvider = getCompletionProvider();
|
||||||
|
int i = (cycle.indexOf(currentProvider)+1) % cycle.size();
|
||||||
|
setCompletionProvider(cycle.get(i));
|
||||||
|
return i==0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Overridden to provide our own implementation of the action.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected Action createAutoCompleteAction() {
|
||||||
|
return new CycleAutoCompleteAction();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resets the cycle to use the default provider on next refresh.
|
||||||
|
*/
|
||||||
|
public void resetProvider() {
|
||||||
|
CompletionProvider currentProvider = getCompletionProvider();
|
||||||
|
CompletionProvider defaultProvider = cycle.get(0);
|
||||||
|
if (currentProvider != defaultProvider) {
|
||||||
|
setCompletionProvider(defaultProvider);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An implementation of the auto-complete action that ensures the proper
|
||||||
|
* <code>CompletionProvider</code> is displayed based on the context in
|
||||||
|
* which the user presses the trigger key.
|
||||||
|
*/
|
||||||
|
private class CycleAutoCompleteAction extends AutoCompleteAction {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void actionPerformed(ActionEvent e) {
|
||||||
|
if (isAutoCompleteEnabled()) {
|
||||||
|
if (isPopupVisible()) {
|
||||||
|
// The popup is already visible, and user pressed the
|
||||||
|
// trigger-key. In this case, move to next provider.
|
||||||
|
advanceProvider();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Be sure to start with the default provider
|
||||||
|
resetProvider();
|
||||||
|
}
|
||||||
|
//Check if there are completions from the current provider. If not, advance to the next provider and display that one.
|
||||||
|
//A completion provider can force displaying "his" empty completion pop-up by returning an empty BasicCompletion. This is useful when the user is typing backspace and you like to display the first provider always first.
|
||||||
|
for (int i=1; i<cycle.size(); i++) {
|
||||||
|
List<Completion> completions = getCompletionProvider().getCompletions(getTextComponent());
|
||||||
|
if (completions.size() > 0) {
|
||||||
|
//nothing to do, just let the current provider display
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
//search for non-empty completions
|
||||||
|
advanceProvider();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
super.actionPerformed(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// TODO add label "Ctrl-Space for <next provider name>" to the popup window
|
||||||
|
}
|
||||||
@@ -0,0 +1,117 @@
|
|||||||
|
/*
|
||||||
|
* 12/22/2008
|
||||||
|
*
|
||||||
|
* ShorhandCompletion.java - A completion that is shorthand for some other
|
||||||
|
* text.
|
||||||
|
*
|
||||||
|
* This library is distributed under a modified BSD license. See the included
|
||||||
|
* AutoComplete.License.txt file for details.
|
||||||
|
*/
|
||||||
|
package org.fife.ui.autocomplete;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A completion where the input text is shorthand for (really, just different
|
||||||
|
* than) the actual text to be inserted. For example, the input text
|
||||||
|
* "<code>sysout</code>" could be associated with the completion
|
||||||
|
* "<code>System.out.println(</code>" in Java.
|
||||||
|
*
|
||||||
|
* @author Robert Futrell
|
||||||
|
* @version 1.0
|
||||||
|
*/
|
||||||
|
public class ShorthandCompletion extends BasicCompletion {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The text the user can start typing that will match this completion.
|
||||||
|
*/
|
||||||
|
private String inputText;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
*
|
||||||
|
* @param provider The provider that returns this completion.
|
||||||
|
* @param inputText The text the user inputs to get this completion.
|
||||||
|
* @param replacementText The replacement text of the completion.
|
||||||
|
*/
|
||||||
|
public ShorthandCompletion(CompletionProvider provider, String inputText,
|
||||||
|
String replacementText) {
|
||||||
|
super(provider, replacementText);
|
||||||
|
this.inputText = inputText;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
*
|
||||||
|
* @param provider The provider that returns this completion.
|
||||||
|
* @param inputText The text the user inputs to get this completion.
|
||||||
|
* @param replacementText The replacement text of the completion.
|
||||||
|
* @param shortDesc A short description of the completion. This will be
|
||||||
|
* displayed in the completion list. This may be <code>null</code>.
|
||||||
|
*/
|
||||||
|
public ShorthandCompletion(CompletionProvider provider, String inputText,
|
||||||
|
String replacementText, String shortDesc) {
|
||||||
|
super(provider, replacementText, shortDesc);
|
||||||
|
this.inputText = inputText;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
*
|
||||||
|
* @param provider The provider that returns this completion.
|
||||||
|
* @param inputText The text the user inputs to get this completion.
|
||||||
|
* @param replacementText The replacement text of the completion.
|
||||||
|
* @param shortDesc A short description of the completion. This will be
|
||||||
|
* displayed in the completion list. This may be <code>null</code>.
|
||||||
|
* @param summary The summary of this completion. This should be HTML.
|
||||||
|
* This may be <code>null</code>.
|
||||||
|
*/
|
||||||
|
public ShorthandCompletion(CompletionProvider provider, String inputText,
|
||||||
|
String replacementText, String shortDesc, String summary) {
|
||||||
|
super(provider, replacementText, shortDesc, summary);
|
||||||
|
this.inputText = inputText;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the text the user must start typing to get this completion.
|
||||||
|
*
|
||||||
|
* @return The text the user must start to input.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String getInputText() {
|
||||||
|
return inputText;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If a summary has been set, that summary is returned. Otherwise, the
|
||||||
|
* replacement text is returned.
|
||||||
|
*
|
||||||
|
* @return A description of this completion (the text that will be
|
||||||
|
* inserted).
|
||||||
|
* @see #getReplacementText()
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String getSummary() {
|
||||||
|
String summary = super.getSummary();
|
||||||
|
return summary!=null ? summary : ("<html><body>" + getSummaryBody());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the "body" of the HTML returned by {@link #getSummary()} when
|
||||||
|
* no summary text has been set. This is defined to return the replacement
|
||||||
|
* text in a monospaced font.
|
||||||
|
*
|
||||||
|
* @return The summary text's body, if no other summary has been defined.
|
||||||
|
* @see #getReplacementText()
|
||||||
|
*/
|
||||||
|
protected String getSummaryBody() {
|
||||||
|
return "<code>" + getReplacementText();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,257 @@
|
|||||||
|
/*
|
||||||
|
* 12/23/2008
|
||||||
|
*
|
||||||
|
* SizeGrip.java - A size grip component that sits at the bottom of the window,
|
||||||
|
* allowing the user to easily resize that window.
|
||||||
|
*
|
||||||
|
* This library is distributed under a modified BSD license. See the included
|
||||||
|
* AutoComplete.License.txt file for details.
|
||||||
|
*/
|
||||||
|
package org.fife.ui.autocomplete;
|
||||||
|
|
||||||
|
import java.awt.Color;
|
||||||
|
import java.awt.ComponentOrientation;
|
||||||
|
import java.awt.Cursor;
|
||||||
|
import java.awt.Dimension;
|
||||||
|
import java.awt.Graphics;
|
||||||
|
import java.awt.Image;
|
||||||
|
import java.awt.Point;
|
||||||
|
import java.awt.Window;
|
||||||
|
import java.awt.event.MouseEvent;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.MalformedURLException;
|
||||||
|
import java.net.URL;
|
||||||
|
import javax.imageio.ImageIO;
|
||||||
|
import javax.swing.JPanel;
|
||||||
|
import javax.swing.SwingUtilities;
|
||||||
|
import javax.swing.UIManager;
|
||||||
|
import javax.swing.event.MouseInputAdapter;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A component that allows its parent window to be resizable, similar to the
|
||||||
|
* size grip seen on status bars. This is essentially a copy of the class with
|
||||||
|
* the same name in RSyntaxTextArea, but is duplicated to prevent a dependency
|
||||||
|
* on that library.
|
||||||
|
*
|
||||||
|
* @author Robert Futrell
|
||||||
|
* @version 1.0
|
||||||
|
*/
|
||||||
|
class SizeGrip extends JPanel {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The size grip to use if we're on OS X.
|
||||||
|
*/
|
||||||
|
private Image osxSizeGrip;
|
||||||
|
|
||||||
|
|
||||||
|
public SizeGrip() {
|
||||||
|
MouseHandler adapter = new MouseHandler();
|
||||||
|
addMouseListener(adapter);
|
||||||
|
addMouseMotionListener(adapter);
|
||||||
|
setPreferredSize(new Dimension(16, 16));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Overridden to ensure that the cursor for this component is appropriate
|
||||||
|
* for the orientation.
|
||||||
|
*
|
||||||
|
* @param o The new orientation.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void applyComponentOrientation(ComponentOrientation o) {
|
||||||
|
possiblyFixCursor(o.isLeftToRight());
|
||||||
|
super.applyComponentOrientation(o);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates and returns the OS X size grip image.
|
||||||
|
*
|
||||||
|
* @return The OS X size grip.
|
||||||
|
*/
|
||||||
|
private Image createOSXSizeGrip() {
|
||||||
|
ClassLoader cl = getClass().getClassLoader();
|
||||||
|
URL url = cl.getResource("org/fife/ui/autocomplete/osx_sizegrip.png");
|
||||||
|
if (url==null) {
|
||||||
|
// We're not running in a jar - we may be debugging in Eclipse,
|
||||||
|
// for example
|
||||||
|
File f = new File("../AutoComplete/src/org/fife/ui/autocomplete/osx_sizegrip.png");
|
||||||
|
if (f.isFile()) {
|
||||||
|
try {
|
||||||
|
url = f.toURI().toURL();
|
||||||
|
} catch (MalformedURLException mue) { // Never happens
|
||||||
|
mue.printStackTrace();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return null; // Can't find resource or image file
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Image image = null;
|
||||||
|
try {
|
||||||
|
image = ImageIO.read(url);
|
||||||
|
} catch (IOException ioe) { // Never happens
|
||||||
|
ioe.printStackTrace();
|
||||||
|
}
|
||||||
|
return image;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Paints this panel.
|
||||||
|
*
|
||||||
|
* @param g The graphics context.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected void paintComponent(Graphics g) {
|
||||||
|
|
||||||
|
super.paintComponent(g);
|
||||||
|
|
||||||
|
Dimension dim = getSize();
|
||||||
|
|
||||||
|
if (osxSizeGrip!=null) {
|
||||||
|
g.drawImage(osxSizeGrip, dim.width-16, dim.height-16, null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Color c1 = UIManager.getColor("Label.disabledShadow");
|
||||||
|
Color c2 = UIManager.getColor("Label.disabledForeground");
|
||||||
|
ComponentOrientation orientation = getComponentOrientation();
|
||||||
|
|
||||||
|
if (orientation.isLeftToRight()) {
|
||||||
|
int width = dim.width -= 3;
|
||||||
|
int height = dim.height -= 3;
|
||||||
|
g.setColor(c1);
|
||||||
|
g.fillRect(width-9,height-1, 3,3);
|
||||||
|
g.fillRect(width-5,height-1, 3,3);
|
||||||
|
g.fillRect(width-1,height-1, 3,3);
|
||||||
|
g.fillRect(width-5,height-5, 3,3);
|
||||||
|
g.fillRect(width-1,height-5, 3,3);
|
||||||
|
g.fillRect(width-1,height-9, 3,3);
|
||||||
|
g.setColor(c2);
|
||||||
|
g.fillRect(width-9,height-1, 2,2);
|
||||||
|
g.fillRect(width-5,height-1, 2,2);
|
||||||
|
g.fillRect(width-1,height-1, 2,2);
|
||||||
|
g.fillRect(width-5,height-5, 2,2);
|
||||||
|
g.fillRect(width-1,height-5, 2,2);
|
||||||
|
g.fillRect(width-1,height-9, 2,2);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
int height = dim.height -= 3;
|
||||||
|
g.setColor(c1);
|
||||||
|
g.fillRect(10,height-1, 3,3);
|
||||||
|
g.fillRect(6,height-1, 3,3);
|
||||||
|
g.fillRect(2,height-1, 3,3);
|
||||||
|
g.fillRect(6,height-5, 3,3);
|
||||||
|
g.fillRect(2,height-5, 3,3);
|
||||||
|
g.fillRect(2,height-9, 3,3);
|
||||||
|
g.setColor(c2);
|
||||||
|
g.fillRect(10,height-1, 2,2);
|
||||||
|
g.fillRect(6,height-1, 2,2);
|
||||||
|
g.fillRect(2,height-1, 2,2);
|
||||||
|
g.fillRect(6,height-5, 2,2);
|
||||||
|
g.fillRect(2,height-5, 2,2);
|
||||||
|
g.fillRect(2,height-9, 2,2);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ensures that the cursor for this component is appropriate for the
|
||||||
|
* orientation.
|
||||||
|
*
|
||||||
|
* @param ltr Whether the current component orientation is LTR.
|
||||||
|
*/
|
||||||
|
protected void possiblyFixCursor(boolean ltr) {
|
||||||
|
int cursor = Cursor.NE_RESIZE_CURSOR;
|
||||||
|
if (ltr) {
|
||||||
|
cursor = Cursor.NW_RESIZE_CURSOR;
|
||||||
|
}
|
||||||
|
if (cursor!=getCursor().getType()) {
|
||||||
|
setCursor(Cursor.getPredefinedCursor(cursor));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void updateUI() {
|
||||||
|
super.updateUI();
|
||||||
|
// TODO: Key off of Aqua LaF, not just OS X, as this size grip looks
|
||||||
|
// bad on other LaFs on Mac such as Nimbus.
|
||||||
|
if (System.getProperty("os.name").contains("OS X")) {
|
||||||
|
if (osxSizeGrip==null) {
|
||||||
|
osxSizeGrip = createOSXSizeGrip();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else { // Clear memory in case of runtime LaF change.
|
||||||
|
osxSizeGrip = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Listens for mouse events on this panel and resizes the parent window
|
||||||
|
* appropriately.
|
||||||
|
*/
|
||||||
|
/*
|
||||||
|
* NOTE: We use SwingUtilities.convertPointToScreen() instead of just using
|
||||||
|
* the locations relative to the corner component because the latter proved
|
||||||
|
* buggy - stretch the window too wide and some kind of arithmetic error
|
||||||
|
* started happening somewhere - our window would grow way too large.
|
||||||
|
*/
|
||||||
|
private class MouseHandler extends MouseInputAdapter {
|
||||||
|
|
||||||
|
private Point origPos;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void mouseDragged(MouseEvent e) {
|
||||||
|
Point newPos = e.getPoint();
|
||||||
|
SwingUtilities.convertPointToScreen(newPos, SizeGrip.this);
|
||||||
|
int xDelta = newPos.x - origPos.x;
|
||||||
|
int yDelta = newPos.y - origPos.y;
|
||||||
|
Window wind = SwingUtilities.getWindowAncestor(SizeGrip.this);
|
||||||
|
if (wind!=null) { // Should always be true
|
||||||
|
if (getComponentOrientation().isLeftToRight()) {
|
||||||
|
int w = wind.getWidth();
|
||||||
|
if (newPos.x>=wind.getX()) {
|
||||||
|
w += xDelta;
|
||||||
|
}
|
||||||
|
int h = wind.getHeight();
|
||||||
|
if (newPos.y>=wind.getY()) {
|
||||||
|
h += yDelta;
|
||||||
|
}
|
||||||
|
wind.setSize(w,h);
|
||||||
|
}
|
||||||
|
else { // RTL
|
||||||
|
int newW = Math.max(1, wind.getWidth()-xDelta);
|
||||||
|
int newH = Math.max(1, wind.getHeight()+yDelta);
|
||||||
|
wind.setBounds(newPos.x, wind.getY(), newW, newH);
|
||||||
|
}
|
||||||
|
// invalidate()/validate() needed pre-1.6.
|
||||||
|
wind.invalidate();
|
||||||
|
wind.validate();
|
||||||
|
}
|
||||||
|
origPos.setLocation(newPos);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void mousePressed(MouseEvent e) {
|
||||||
|
origPos = e.getPoint();
|
||||||
|
SwingUtilities.convertPointToScreen(origPos, SizeGrip.this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void mouseReleased(MouseEvent e) {
|
||||||
|
origPos = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
/*
|
||||||
|
* 12/17/2010
|
||||||
|
*
|
||||||
|
* SortByRelevanceComparator.java - Sorts two Completions by relevance before
|
||||||
|
* sorting them lexicographically.
|
||||||
|
*
|
||||||
|
* This library is distributed under a modified BSD license. See the included
|
||||||
|
* AutoComplete.License.txt file for details.
|
||||||
|
*/
|
||||||
|
package org.fife.ui.autocomplete;
|
||||||
|
|
||||||
|
import java.util.Comparator;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compares two <code>Completion</code>s by their relevance before
|
||||||
|
* sorting them lexicographically.
|
||||||
|
*
|
||||||
|
* @author Robert Futrell
|
||||||
|
* @version 1.0
|
||||||
|
*/
|
||||||
|
public class SortByRelevanceComparator implements Comparator<Completion> {
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int compare(Completion c1, Completion c2) {
|
||||||
|
int rel1 = c1.getRelevance();
|
||||||
|
int rel2 = c2.getRelevance();
|
||||||
|
int diff = rel2 - rel1;//rel1 - rel2;
|
||||||
|
return diff==0 ? c1.compareTo(c2) : diff;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,391 @@
|
|||||||
|
/*
|
||||||
|
* 05/26/2012
|
||||||
|
*
|
||||||
|
* TemplateCompletion.java - A completion used to insert boilerplate code
|
||||||
|
* snippets that have arbitrary sections the user will want to change, such as
|
||||||
|
* for-loops.
|
||||||
|
*
|
||||||
|
* This library is distributed under a modified BSD license. See the included
|
||||||
|
* AutoComplete.License.txt file for details.
|
||||||
|
*/
|
||||||
|
package org.fife.ui.autocomplete;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import javax.swing.text.BadLocationException;
|
||||||
|
import javax.swing.text.Document;
|
||||||
|
import javax.swing.text.JTextComponent;
|
||||||
|
import javax.swing.text.PlainDocument;
|
||||||
|
import javax.swing.text.Position;
|
||||||
|
|
||||||
|
import org.fife.ui.autocomplete.TemplatePiece.Param;
|
||||||
|
import org.fife.ui.autocomplete.TemplatePiece.ParamCopy;
|
||||||
|
import org.fife.ui.autocomplete.TemplatePiece.Text;
|
||||||
|
import org.fife.ui.rsyntaxtextarea.RSyntaxUtilities;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A completion made up of a template with arbitrary parameters that the user
|
||||||
|
* can tab through and fill in. This completion type is useful for inserting
|
||||||
|
* common boilerplate code, such as for-loops.<p>
|
||||||
|
*
|
||||||
|
* The format of a template is similar to those in Eclipse. The following
|
||||||
|
* example would be the format for a for-loop template:
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* for (int ${i} = 0; ${i} < ${array}.length; ${i}++) {
|
||||||
|
* ${cursor}
|
||||||
|
* }
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* In the above example, the first <code>${i}</code> is a parameter for the
|
||||||
|
* user to type into; all the other <code>${i}</code> instances are
|
||||||
|
* automatically changed to what the user types in the first one. The parameter
|
||||||
|
* named <code>${cursor}</code> is the "ending position" of the template. It's
|
||||||
|
* where the caret moves after it cycles through all other parameters. If the
|
||||||
|
* user types into it, template mode terminates. If more than one
|
||||||
|
* <code>${cursor}</code> parameter is specified, behavior is undefined.<p>
|
||||||
|
*
|
||||||
|
* Two dollar signs in a row ("<code>$$</code>") will be evaluated as a single
|
||||||
|
* dollar sign. Otherwise, the template parsing is pretty straightforward and
|
||||||
|
* fault-tolerant.<p>
|
||||||
|
*
|
||||||
|
* Leading whitespace is automatically added to lines if the template spans
|
||||||
|
* more than one line, and if used with a text component using a
|
||||||
|
* <code>PlainDocument</code>, tabs will be converted to spaces if requested.
|
||||||
|
*
|
||||||
|
* @author Robert Futrell
|
||||||
|
* @version 1.0
|
||||||
|
*/
|
||||||
|
public class TemplateCompletion extends AbstractCompletion
|
||||||
|
implements ParameterizedCompletion {
|
||||||
|
|
||||||
|
private List<TemplatePiece> pieces;
|
||||||
|
|
||||||
|
private String inputText;
|
||||||
|
|
||||||
|
private String definitionString;
|
||||||
|
|
||||||
|
private String shortDescription;
|
||||||
|
|
||||||
|
private String summary;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The template's parameters.
|
||||||
|
*/
|
||||||
|
private List<Parameter> params;
|
||||||
|
|
||||||
|
|
||||||
|
public TemplateCompletion(CompletionProvider provider,
|
||||||
|
String inputText, String definitionString, String template) {
|
||||||
|
this(provider, inputText, definitionString, template, null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public TemplateCompletion(CompletionProvider provider,
|
||||||
|
String inputText, String definitionString, String template,
|
||||||
|
String shortDescription, String summary) {
|
||||||
|
super(provider);
|
||||||
|
this.inputText = inputText;
|
||||||
|
this.definitionString = definitionString;
|
||||||
|
this.shortDescription = shortDescription;
|
||||||
|
this.summary = summary;
|
||||||
|
pieces = new ArrayList<TemplatePiece>(3);
|
||||||
|
params = new ArrayList<Parameter>(3);
|
||||||
|
parse(template);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void addTemplatePiece(TemplatePiece piece) {
|
||||||
|
pieces.add(piece);
|
||||||
|
if (piece instanceof Param && !"cursor".equals(piece.getText())) {
|
||||||
|
final String type = null; // TODO
|
||||||
|
Parameter param = new Parameter(type, piece.getText());
|
||||||
|
params.add(param);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getInputText() {
|
||||||
|
return inputText;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private String getPieceText(int index, String leadingWS) {
|
||||||
|
TemplatePiece piece = pieces.get(index);
|
||||||
|
String text = piece.getText();
|
||||||
|
if (text.indexOf('\n')>-1) {
|
||||||
|
text = text.replaceAll("\n", "\n" + leadingWS);
|
||||||
|
}
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns <code>null</code>; template completions insert all of their
|
||||||
|
* text via <code>getInsertionInfo()</code>.
|
||||||
|
*
|
||||||
|
* @return <code>null</code> always.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String getReplacementText() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getSummary() {
|
||||||
|
return summary;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDefinitionString() {
|
||||||
|
return definitionString;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getShortDescription() {
|
||||||
|
return shortDescription;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean getShowParameterToolTip() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ParameterizedCompletionInsertionInfo getInsertionInfo(
|
||||||
|
JTextComponent tc, boolean replaceTabsWithSpaces) {
|
||||||
|
|
||||||
|
ParameterizedCompletionInsertionInfo info =
|
||||||
|
new ParameterizedCompletionInsertionInfo();
|
||||||
|
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
int dot = tc.getCaretPosition();
|
||||||
|
|
||||||
|
// Get the range in which the caret can move before we hide
|
||||||
|
// this tool tip.
|
||||||
|
int minPos = dot;
|
||||||
|
Position maxPos = null;
|
||||||
|
int defaultEndOffs = -1;
|
||||||
|
try {
|
||||||
|
maxPos = tc.getDocument().createPosition(dot);
|
||||||
|
} catch (BadLocationException ble) {
|
||||||
|
ble.printStackTrace(); // Never happens
|
||||||
|
}
|
||||||
|
info.setCaretRange(minPos, maxPos);
|
||||||
|
int selStart = dot; // Default value
|
||||||
|
int selEnd = selStart;
|
||||||
|
|
||||||
|
Document doc = tc.getDocument();
|
||||||
|
String leadingWS = null;
|
||||||
|
try {
|
||||||
|
leadingWS = RSyntaxUtilities.getLeadingWhitespace(doc, dot);
|
||||||
|
} catch (BadLocationException ble) { // Never happens
|
||||||
|
ble.printStackTrace();
|
||||||
|
leadingWS = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the text to insert (keep it one completion for
|
||||||
|
// performance and simplicity of undo/redo).
|
||||||
|
int start = dot;
|
||||||
|
for (int i=0; i<pieces.size(); i++) {
|
||||||
|
TemplatePiece piece = pieces.get(i);
|
||||||
|
String text = getPieceText(i, leadingWS);
|
||||||
|
if (piece instanceof Text) {
|
||||||
|
if (replaceTabsWithSpaces) {
|
||||||
|
start = possiblyReplaceTabsWithSpaces(sb, text, tc, start);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
sb.append(text);
|
||||||
|
start += text.length();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (piece instanceof Param && "cursor".equals(text)) {
|
||||||
|
defaultEndOffs = start;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
int end = start + text.length();
|
||||||
|
sb.append(text);
|
||||||
|
if (piece instanceof Param) {
|
||||||
|
info.addReplacementLocation(start, end);
|
||||||
|
if (selStart==dot) {
|
||||||
|
selStart = start;
|
||||||
|
selEnd = selStart + text.length();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (piece instanceof ParamCopy) {
|
||||||
|
info.addReplacementCopy(piece.getText(), start, end);
|
||||||
|
}
|
||||||
|
start = end;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Highlight the first parameter. If no params were specified, move
|
||||||
|
// the caret to the ${cursor} location, if specified
|
||||||
|
if (selStart==minPos && selStart==selEnd && getParamCount()==0) {
|
||||||
|
if (defaultEndOffs>-1) { // ${cursor} specified
|
||||||
|
selStart = selEnd = defaultEndOffs;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
info.setInitialSelection(selStart, selEnd);
|
||||||
|
|
||||||
|
if (defaultEndOffs>-1) {
|
||||||
|
// Keep this location "after" all others when tabbing
|
||||||
|
info.addReplacementLocation(defaultEndOffs, defaultEndOffs);
|
||||||
|
}
|
||||||
|
info.setDefaultEndOffs(defaultEndOffs);
|
||||||
|
info.setTextToInsert(sb.toString());
|
||||||
|
|
||||||
|
return info;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Parameter getParam(int index) {
|
||||||
|
return params.get(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public int getParamCount() {
|
||||||
|
return params==null ? 0 : params.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether a parameter is already defined with a specific name.
|
||||||
|
*
|
||||||
|
* @param name The name.
|
||||||
|
* @return Whether a parameter is defined with that name.
|
||||||
|
*/
|
||||||
|
private boolean isParamDefined(String name) {
|
||||||
|
for (int i=0; i<getParamCount(); i++) {
|
||||||
|
Parameter param = getParam(i);
|
||||||
|
if (name.equals(param.getName())) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses a template string into logical pieces used by this class.
|
||||||
|
*
|
||||||
|
* @param template The template to parse.
|
||||||
|
*/
|
||||||
|
private void parse(String template) {
|
||||||
|
|
||||||
|
int offs = 0;
|
||||||
|
int lastOffs = 0;
|
||||||
|
|
||||||
|
while ((offs=template.indexOf('$', lastOffs))>-1 && offs<template.length()-1) {
|
||||||
|
|
||||||
|
char next = template.charAt(offs+1);
|
||||||
|
switch (next) {
|
||||||
|
case '$': // "$$" => escaped single dollar sign
|
||||||
|
addTemplatePiece(new TemplatePiece.Text(
|
||||||
|
template.substring(lastOffs, offs+1)));
|
||||||
|
lastOffs = offs + 2;
|
||||||
|
break;
|
||||||
|
case '{': // "${...}" => variable
|
||||||
|
int closingCurly = template.indexOf('}', offs+2);
|
||||||
|
if (closingCurly>-1) {
|
||||||
|
addTemplatePiece(new TemplatePiece.Text(
|
||||||
|
template.substring(lastOffs, offs)));
|
||||||
|
String varName = template.substring(offs+2, closingCurly);
|
||||||
|
if (!"cursor".equals(varName) && isParamDefined(varName)) {
|
||||||
|
addTemplatePiece(new TemplatePiece.ParamCopy(varName));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
addTemplatePiece(new TemplatePiece.Param(varName));
|
||||||
|
}
|
||||||
|
lastOffs = closingCurly + 1;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lastOffs<template.length()) {
|
||||||
|
String text = template.substring(lastOffs);
|
||||||
|
addTemplatePiece(new TemplatePiece.Text(text));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private int possiblyReplaceTabsWithSpaces(StringBuilder sb, String text,
|
||||||
|
JTextComponent tc, int start) {
|
||||||
|
|
||||||
|
int tab = text.indexOf('\t');
|
||||||
|
if (tab>-1) {
|
||||||
|
|
||||||
|
int startLen = sb.length();
|
||||||
|
|
||||||
|
int size = 4;
|
||||||
|
Document doc = tc.getDocument();
|
||||||
|
if (doc != null) {
|
||||||
|
Integer i = (Integer) doc.getProperty(PlainDocument.tabSizeAttribute);
|
||||||
|
if (i != null) {
|
||||||
|
size = i.intValue();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
String tabStr = "";
|
||||||
|
for (int i=0; i<size; i++) {
|
||||||
|
tabStr += " ";
|
||||||
|
}
|
||||||
|
|
||||||
|
int lastOffs = 0;
|
||||||
|
do {
|
||||||
|
sb.append(text.substring(lastOffs, tab));
|
||||||
|
sb.append(tabStr);
|
||||||
|
lastOffs = tab + 1;
|
||||||
|
} while ((tab=text.indexOf('\t', lastOffs))>-1);
|
||||||
|
sb.append(text.substring(lastOffs));
|
||||||
|
|
||||||
|
start += sb.length() - startLen;
|
||||||
|
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
sb.append(text);
|
||||||
|
start += text.length();
|
||||||
|
}
|
||||||
|
|
||||||
|
return start;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the short description of this template completion.
|
||||||
|
*
|
||||||
|
* @param shortDesc The new short description.
|
||||||
|
* @see #getShortDescription()
|
||||||
|
*/
|
||||||
|
public void setShortDescription(String shortDesc) {
|
||||||
|
this.shortDescription = shortDesc;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return getDefinitionString();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
/*
|
||||||
|
* 06/17/2012
|
||||||
|
*
|
||||||
|
* TemplatePiece.java - A logical piece of a template completion.
|
||||||
|
*
|
||||||
|
* This library is distributed under a modified BSD license. See the included
|
||||||
|
* AutoComplete.License.txt file for details.
|
||||||
|
*/
|
||||||
|
package org.fife.ui.autocomplete;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A piece of a <code>TemplateCompletion</code>. You add instances of this
|
||||||
|
* class to template completions to define them.
|
||||||
|
*
|
||||||
|
* @author Robert Futrell
|
||||||
|
* @version 1.0
|
||||||
|
* @see TemplateCompletion
|
||||||
|
*/
|
||||||
|
interface TemplatePiece {
|
||||||
|
|
||||||
|
|
||||||
|
String getText();
|
||||||
|
|
||||||
|
|
||||||
|
public class Text implements TemplatePiece {
|
||||||
|
|
||||||
|
private String text;
|
||||||
|
|
||||||
|
public Text(String text) {
|
||||||
|
this.text = text;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getText() {
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "[TemplatePiece.Text: text=" + text + "]";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public class Param implements TemplatePiece {
|
||||||
|
|
||||||
|
String text;
|
||||||
|
|
||||||
|
public Param(String text) {
|
||||||
|
this.text = text;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getText() {
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "[TemplatePiece.Param: param=" + text + "]";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public class ParamCopy implements TemplatePiece {
|
||||||
|
|
||||||
|
private String text;
|
||||||
|
|
||||||
|
public ParamCopy(String text) {
|
||||||
|
this.text = text;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getText() {
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "[TemplatePiece.ParamCopy: param=" + text + "]";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,179 @@
|
|||||||
|
/*
|
||||||
|
* 08/13/2009
|
||||||
|
*
|
||||||
|
* TipUtil.java - Utility methods for homemade tool tips.
|
||||||
|
*
|
||||||
|
* This library is distributed under a modified BSD license. See the included
|
||||||
|
* AutoComplete.License.txt file for details.
|
||||||
|
*/
|
||||||
|
package org.fife.ui.autocomplete;
|
||||||
|
|
||||||
|
import java.awt.Color;
|
||||||
|
import java.awt.Font;
|
||||||
|
import java.awt.SystemColor;
|
||||||
|
import java.net.URL;
|
||||||
|
import javax.swing.BorderFactory;
|
||||||
|
import javax.swing.JEditorPane;
|
||||||
|
import javax.swing.UIManager;
|
||||||
|
import javax.swing.border.Border;
|
||||||
|
import javax.swing.plaf.ColorUIResource;
|
||||||
|
import javax.swing.text.html.HTMLDocument;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Static utility methods for homemade tool tips.<p>
|
||||||
|
*
|
||||||
|
* This is blatantly ripped off from RSyntaxTextArea's "FocusableTips" class
|
||||||
|
* of the same name, but isn't re-used to prevent a hard dependency on the
|
||||||
|
* RSTA library.
|
||||||
|
*
|
||||||
|
* @author Robert Futrell
|
||||||
|
* @version 1.0
|
||||||
|
*/
|
||||||
|
class TipUtil {
|
||||||
|
|
||||||
|
|
||||||
|
private TipUtil() {
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the default background color to use for tool tip windows.
|
||||||
|
*
|
||||||
|
* @return The default background color.
|
||||||
|
*/
|
||||||
|
public static Color getToolTipBackground() {
|
||||||
|
|
||||||
|
Color c = UIManager.getColor("ToolTip.background");
|
||||||
|
|
||||||
|
// Tooltip.background is wrong color on Nimbus (!)
|
||||||
|
boolean isNimbus = isNimbusLookAndFeel();
|
||||||
|
if (c==null || isNimbus) {
|
||||||
|
c = UIManager.getColor("info"); // Used by Nimbus (and others)
|
||||||
|
if (c==null || (isNimbus && isDerivedColor(c))) {
|
||||||
|
c = SystemColor.info; // System default
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Workaround for a bug (?) with Nimbus - calling JLabel.setBackground()
|
||||||
|
// with a ColorUIResource does nothing, must be a normal Color
|
||||||
|
if (c instanceof ColorUIResource) {
|
||||||
|
c = new Color(c.getRGB());
|
||||||
|
}
|
||||||
|
|
||||||
|
return c;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the border used by tool tips in this look and feel.
|
||||||
|
*
|
||||||
|
* @return The border.
|
||||||
|
*/
|
||||||
|
public static Border getToolTipBorder() {
|
||||||
|
|
||||||
|
Border border = UIManager.getBorder("ToolTip.border");
|
||||||
|
|
||||||
|
if (border==null || isNimbusLookAndFeel()) {
|
||||||
|
border = UIManager.getBorder("nimbusBorder");
|
||||||
|
if (border==null) {
|
||||||
|
border = BorderFactory.createLineBorder(SystemColor.controlDkShadow);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return border;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether a color is a Nimbus DerivedColor, which is troublesome
|
||||||
|
* in that it doesn't use its RGB values (uses HSB instead?) and so
|
||||||
|
* querying them is useless.
|
||||||
|
*
|
||||||
|
* @param c The color to check.
|
||||||
|
* @return Whether it is a DerivedColor
|
||||||
|
*/
|
||||||
|
private static final boolean isDerivedColor(Color c) {
|
||||||
|
return c!=null && (c.getClass().getName().endsWith(".DerivedColor") ||
|
||||||
|
c.getClass().getName().endsWith(".DerivedColor$UIResource"));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether the Nimbus Look and Feel is installed.
|
||||||
|
*
|
||||||
|
* @return Whether the current LAF is Nimbus.
|
||||||
|
*/
|
||||||
|
private static final boolean isNimbusLookAndFeel() {
|
||||||
|
return UIManager.getLookAndFeel().getName().equals("Nimbus");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tweaks a <code>JEditorPane</code> so it can be used to render the
|
||||||
|
* content in a focusable pseudo-tool tip. It is assumed that the editor
|
||||||
|
* pane is using an <code>HTMLDocument</code>.
|
||||||
|
*
|
||||||
|
* @param textArea The editor pane to tweak.
|
||||||
|
*/
|
||||||
|
public static void tweakTipEditorPane(JEditorPane textArea) {
|
||||||
|
|
||||||
|
// Jump through a few hoops to get things looking nice in Nimbus
|
||||||
|
boolean isNimbus = isNimbusLookAndFeel();
|
||||||
|
if (isNimbus) {
|
||||||
|
Color selBG = textArea.getSelectionColor();
|
||||||
|
Color selFG = textArea.getSelectedTextColor();
|
||||||
|
textArea.setUI(new javax.swing.plaf.basic.BasicEditorPaneUI());
|
||||||
|
textArea.setSelectedTextColor(selFG);
|
||||||
|
textArea.setSelectionColor(selBG);
|
||||||
|
}
|
||||||
|
|
||||||
|
textArea.setEditable(false); // Required for links to work!
|
||||||
|
textArea.setBorder(BorderFactory.createEmptyBorder(5,5,5,5));
|
||||||
|
|
||||||
|
// Make selection visible even though we are not (initially) focusable.
|
||||||
|
textArea.getCaret().setSelectionVisible(true);
|
||||||
|
|
||||||
|
// Set the foreground color. Important because when rendering HTML,
|
||||||
|
// default foreground becomes black, which may not match all LAF's
|
||||||
|
// (e.g. Substance).
|
||||||
|
Color fg = UIManager.getColor("Label.foreground");
|
||||||
|
if (fg==null || (isNimbus && isDerivedColor(fg))) {
|
||||||
|
fg = SystemColor.textText;
|
||||||
|
}
|
||||||
|
textArea.setForeground(fg);
|
||||||
|
|
||||||
|
// Make it use the "tool tip" background color.
|
||||||
|
textArea.setBackground(TipUtil.getToolTipBackground());
|
||||||
|
|
||||||
|
// Force JEditorPane to use a certain font even in HTML.
|
||||||
|
// All standard LookAndFeels, even Nimbus (!), define Label.font.
|
||||||
|
Font font = UIManager.getFont("Label.font");
|
||||||
|
if (font == null) { // Try to make a sensible default
|
||||||
|
font = new Font("SansSerif", Font.PLAIN, 12);
|
||||||
|
}
|
||||||
|
HTMLDocument doc = (HTMLDocument) textArea.getDocument();
|
||||||
|
doc.getStyleSheet().addRule(
|
||||||
|
"body { font-family: " + font.getFamily() +
|
||||||
|
"; font-size: " + font.getSize() + "pt" +
|
||||||
|
"; color: " + Util.getHexString(fg) + "; }");
|
||||||
|
|
||||||
|
// Always add link foreground rule. Unfortunately these CSS rules
|
||||||
|
// stack each time the LaF is changed (how can we overwrite them
|
||||||
|
// without clearing out the important "standard" ones?).
|
||||||
|
Color linkFG = Util.getHyperlinkForeground();
|
||||||
|
doc.getStyleSheet().addRule(
|
||||||
|
"a { color: " + Util.getHexString(linkFG) + "; }");
|
||||||
|
|
||||||
|
URL url = TipUtil.class.getResource("bullet_black.png");
|
||||||
|
if (url!=null) {
|
||||||
|
doc.getStyleSheet().addRule(
|
||||||
|
"ul { list-style-image: '" + url.toString() + "'; }");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,323 @@
|
|||||||
|
/*
|
||||||
|
* 12/21/2008
|
||||||
|
*
|
||||||
|
* Util.java - Utility methods for the autocompletion package.
|
||||||
|
*
|
||||||
|
* This library is distributed under a modified BSD license. See the included
|
||||||
|
* AutoComplete.License.txt file for details.
|
||||||
|
*/
|
||||||
|
package org.fife.ui.autocomplete;
|
||||||
|
|
||||||
|
import java.awt.Color;
|
||||||
|
import java.awt.GraphicsConfiguration;
|
||||||
|
import java.awt.GraphicsDevice;
|
||||||
|
import java.awt.GraphicsEnvironment;
|
||||||
|
import java.awt.Rectangle;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.security.AccessControlException;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
import javax.swing.JLabel;
|
||||||
|
import javax.swing.UIManager;
|
||||||
|
|
||||||
|
import org.fife.ui.rsyntaxtextarea.PopupWindowDecorator;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility methods for the auto-complete framework.
|
||||||
|
*
|
||||||
|
* @author Robert Futrell
|
||||||
|
* @version 1.0
|
||||||
|
*/
|
||||||
|
public class Util {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If a system property is defined with this name and set, ignoring case,
|
||||||
|
* to <code>true</code>, this library will not attempt to use Substance
|
||||||
|
* renderers. Otherwise, if a Substance Look and Feel is installed, we
|
||||||
|
* will attempt to use Substance cell renderers in all of our dropdowns.<p>
|
||||||
|
*
|
||||||
|
* Note that we do not have a build dependency on Substance, so all access
|
||||||
|
* to Substance stuff is done via reflection. We will fall back onto
|
||||||
|
* default renderers if something goes horribly wrong.
|
||||||
|
*/
|
||||||
|
public static final String PROPERTY_DONT_USE_SUBSTANCE_RENDERERS =
|
||||||
|
"org.fife.ui.autocomplete.DontUseSubstanceRenderers";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If this system property is <code>true</code>, then even the "main" two
|
||||||
|
* auto-complete windows will allow window decorations via
|
||||||
|
* {@link PopupWindowDecorator}. If this property is undefined or
|
||||||
|
* <code>false</code>, they won't honor such decorations. This is due to
|
||||||
|
* certain performance issues with translucent windows (used for drop
|
||||||
|
* shadows), even as of Java 7u2.
|
||||||
|
*/
|
||||||
|
public static final String PROPERTY_ALLOW_DECORATED_AUTOCOMPLETE_WINDOWS =
|
||||||
|
"org.fife.ui.autocomplete.allowDecoratedAutoCompleteWindows";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used for the color of hyperlinks when a LookAndFeel uses light text
|
||||||
|
* against a dark background.
|
||||||
|
*/
|
||||||
|
private static final Color LIGHT_HYPERLINK_FG = new Color(0xd8ffff);
|
||||||
|
|
||||||
|
private static final Pattern TAG_PATTERN = Pattern.compile("<[^>]*>");
|
||||||
|
|
||||||
|
private static final boolean useSubstanceRenderers;
|
||||||
|
private static boolean desktopCreationAttempted;
|
||||||
|
private static Object desktop;
|
||||||
|
private static final Object LOCK_DESKTOP_CREATION = new Object();
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attempts to open a web browser to the specified URI.
|
||||||
|
*
|
||||||
|
* @param uri The URI to open. If this is <code>null</code>, nothing
|
||||||
|
happens and this method returns <code>false</code>.
|
||||||
|
* @return Whether the operation was successful. This will be
|
||||||
|
* <code>false</code> on JRE's older than 1.6.
|
||||||
|
*/
|
||||||
|
public static boolean browse(URI uri) {
|
||||||
|
|
||||||
|
boolean success = false;
|
||||||
|
|
||||||
|
if (uri!=null) {
|
||||||
|
Object desktop = getDesktop();
|
||||||
|
if (desktop!=null) {
|
||||||
|
try {
|
||||||
|
Method m = desktop.getClass().getDeclaredMethod(
|
||||||
|
"browse", new Class[] { URI.class });
|
||||||
|
m.invoke(desktop, new Object[] { uri });
|
||||||
|
success = true;
|
||||||
|
} catch (RuntimeException re) {
|
||||||
|
throw re; // Keep FindBugs happy
|
||||||
|
} catch (Exception e) {
|
||||||
|
// Ignore, just return "false" below.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return success;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the singleton <code>java.awt.Desktop</code> instance, or
|
||||||
|
* <code>null</code> if it is unsupported on this platform (or the JRE
|
||||||
|
* is older than 1.6).
|
||||||
|
*
|
||||||
|
* @return The desktop, as an {@link Object}.
|
||||||
|
*/
|
||||||
|
private static Object getDesktop() {
|
||||||
|
|
||||||
|
synchronized (LOCK_DESKTOP_CREATION) {
|
||||||
|
|
||||||
|
if (!desktopCreationAttempted) {
|
||||||
|
|
||||||
|
desktopCreationAttempted = true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
Class<?> desktopClazz = Class.forName("java.awt.Desktop");
|
||||||
|
Method m = desktopClazz.
|
||||||
|
getDeclaredMethod("isDesktopSupported");
|
||||||
|
|
||||||
|
boolean supported= ((Boolean)m.invoke(null)).booleanValue();
|
||||||
|
if (supported) {
|
||||||
|
m = desktopClazz.getDeclaredMethod("getDesktop");
|
||||||
|
desktop = m.invoke(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (RuntimeException re) {
|
||||||
|
throw re; // Keep FindBugs happy
|
||||||
|
} catch (Exception e) {
|
||||||
|
// Ignore; keeps desktop as null.
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return desktop;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a hex string for the specified color, suitable for HTML.
|
||||||
|
*
|
||||||
|
* @param c The color.
|
||||||
|
* @return The string representation, in the form "<code>#rrggbb</code>",
|
||||||
|
* or <code>null</code> if <code>c</code> is <code>null</code>.
|
||||||
|
*/
|
||||||
|
public static String getHexString(Color c) {
|
||||||
|
|
||||||
|
if (c==null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't assume 0xff alpha
|
||||||
|
//return "#" + Integer.toHexString(c.getRGB()&0xffffff).substring(2);
|
||||||
|
|
||||||
|
StringBuilder sb = new StringBuilder("#");
|
||||||
|
int r = c.getRed();
|
||||||
|
if (r<16) {
|
||||||
|
sb.append('0');
|
||||||
|
}
|
||||||
|
sb.append(Integer.toHexString(r));
|
||||||
|
int g = c.getGreen();
|
||||||
|
if (g<16) {
|
||||||
|
sb.append('0');
|
||||||
|
}
|
||||||
|
sb.append(Integer.toHexString(g));
|
||||||
|
int b = c.getBlue();
|
||||||
|
if (b<16) {
|
||||||
|
sb.append('0');
|
||||||
|
}
|
||||||
|
sb.append(Integer.toHexString(b));
|
||||||
|
|
||||||
|
return sb.toString();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the color to use for hyperlink-style components. This method
|
||||||
|
* will return <code>Color.blue</code> unless it appears that the current
|
||||||
|
* LookAndFeel uses light text on a dark background, in which case a
|
||||||
|
* brighter alternative is returned.
|
||||||
|
*
|
||||||
|
* @return The color to use for hyperlinks.
|
||||||
|
*/
|
||||||
|
static final Color getHyperlinkForeground() {
|
||||||
|
|
||||||
|
// This property is defined by all standard LaFs, even Nimbus (!),
|
||||||
|
// but you never know what crazy LaFs there are...
|
||||||
|
Color fg = UIManager.getColor("Label.foreground");
|
||||||
|
if (fg==null) {
|
||||||
|
fg = new JLabel().getForeground();
|
||||||
|
}
|
||||||
|
|
||||||
|
return isLightForeground(fg) ? LIGHT_HYPERLINK_FG : Color.blue;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the screen coordinates for the monitor that contains the
|
||||||
|
* specified point. This is useful for setups with multiple monitors,
|
||||||
|
* to ensure that popup windows are positioned properly.
|
||||||
|
*
|
||||||
|
* @param x The x-coordinate, in screen coordinates.
|
||||||
|
* @param y The y-coordinate, in screen coordinates.
|
||||||
|
* @return The bounds of the monitor that contains the specified point.
|
||||||
|
*/
|
||||||
|
public static Rectangle getScreenBoundsForPoint(int x, int y) {
|
||||||
|
GraphicsEnvironment env = GraphicsEnvironment.
|
||||||
|
getLocalGraphicsEnvironment();
|
||||||
|
GraphicsDevice[] devices = env.getScreenDevices();
|
||||||
|
for (int i=0; i<devices.length; i++) {
|
||||||
|
GraphicsConfiguration config = devices[i].getDefaultConfiguration();
|
||||||
|
Rectangle gcBounds = config.getBounds();
|
||||||
|
if (gcBounds.contains(x, y)) {
|
||||||
|
return gcBounds;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// If point is outside all monitors, default to default monitor (?)
|
||||||
|
return env.getMaximumWindowBounds();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Give apps a chance to decorate us with drop shadows, etc. Since very
|
||||||
|
* scrolly things such as lists (of e.g. completions) are *very* slow when
|
||||||
|
* in per-pixel translucent windows, even as of Java 7u2, we force the user
|
||||||
|
* to specify an extra option for the two "main" auto-complete windows.
|
||||||
|
*
|
||||||
|
* @return Whether to allow decorating the main auto-complete windows.
|
||||||
|
* @see #PROPERTY_ALLOW_DECORATED_AUTOCOMPLETE_WINDOWS
|
||||||
|
*/
|
||||||
|
public static boolean getShouldAllowDecoratingMainAutoCompleteWindows() {
|
||||||
|
try {
|
||||||
|
return Boolean.getBoolean(
|
||||||
|
PROPERTY_ALLOW_DECORATED_AUTOCOMPLETE_WINDOWS);
|
||||||
|
} catch (AccessControlException ace) { // We're in an applet.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether we should attempt to use Substance cell renderers and
|
||||||
|
* styles for things such as completion choices, if a Substance Look and
|
||||||
|
* Feel is installed. If this is <code>false</code>, we'll use our
|
||||||
|
* standard rendering for completions, even when Substance is being used.
|
||||||
|
*
|
||||||
|
* @return Whether to use Substance renderers if Substance is installed.
|
||||||
|
*/
|
||||||
|
public static boolean getUseSubstanceRenderers() {
|
||||||
|
return useSubstanceRenderers;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether the specified color is "light" to use as a foreground.
|
||||||
|
* Colors that return <code>true</code> indicate that the current Look and
|
||||||
|
* Feel probably uses light text colors on a dark background.
|
||||||
|
*
|
||||||
|
* @param fg The foreground color.
|
||||||
|
* @return Whether it is a "light" foreground color.
|
||||||
|
*/
|
||||||
|
public static final boolean isLightForeground(Color fg) {
|
||||||
|
return fg.getRed()>0xa0 && fg.getGreen()>0xa0 && fg.getBlue()>0xa0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether a string starts with a specified prefix, ignoring case.
|
||||||
|
* This method does not support characters outside of the BMP.
|
||||||
|
*
|
||||||
|
* @param str The string to check. This cannot be {@code null}.
|
||||||
|
* @param prefix The prefix to check for. This cannot be {@code null}.
|
||||||
|
* @return Whether {@code str} starts with {@code prefix}, ignoring case.
|
||||||
|
*/
|
||||||
|
public static boolean startsWithIgnoreCase(String str, String prefix) {
|
||||||
|
int prefixLength = prefix.length();
|
||||||
|
if (str.length() >= prefixLength) {
|
||||||
|
return str.regionMatches(true, 0, prefix, 0, prefixLength);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Strips any HTML from a string. The string must start with
|
||||||
|
* "<code><html></code>" for markup tags to be stripped.
|
||||||
|
*
|
||||||
|
* @param text The string.
|
||||||
|
* @return The string, with any HTML stripped.
|
||||||
|
*/
|
||||||
|
public static String stripHtml(String text) {
|
||||||
|
if (text==null || !text.startsWith("<html>")) {
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
// TODO: Micro-optimize me, might be called in renderers and loops
|
||||||
|
return TAG_PATTERN.matcher(text).replaceAll("");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static {
|
||||||
|
|
||||||
|
boolean use = true;
|
||||||
|
try {
|
||||||
|
use = !Boolean.getBoolean(PROPERTY_DONT_USE_SUBSTANCE_RENDERERS);
|
||||||
|
} catch (AccessControlException ace) { // We're in an applet.
|
||||||
|
use = true;
|
||||||
|
}
|
||||||
|
useSubstanceRenderers = use;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,196 @@
|
|||||||
|
/*
|
||||||
|
* 12/22/2008
|
||||||
|
*
|
||||||
|
* VariableCompletion.java - A completion for a variable.
|
||||||
|
*
|
||||||
|
* This library is distributed under a modified BSD license. See the included
|
||||||
|
* AutoComplete.License.txt file for details.
|
||||||
|
*/
|
||||||
|
package org.fife.ui.autocomplete;
|
||||||
|
|
||||||
|
import javax.swing.text.JTextComponent;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A completion for a variable (or constant) in a programming language.<p>
|
||||||
|
*
|
||||||
|
* This completion type uses its <tt>shortDescription</tt> property as part of
|
||||||
|
* its summary returned by {@link #getSummary()}; for this reason, it may be
|
||||||
|
* a little longer (even much longer), if desired, than what is recommended
|
||||||
|
* for <tt>BasicCompletion</tt>s (where the <tt>shortDescription</tt> is used
|
||||||
|
* in {@link #toString()} for <tt>ListCellRenderers</tt>).
|
||||||
|
*
|
||||||
|
* @author Robert Futrell
|
||||||
|
* @version 1.0
|
||||||
|
*/
|
||||||
|
public class VariableCompletion extends BasicCompletion {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The variable's type.
|
||||||
|
*/
|
||||||
|
private String type;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* What library (for example) this variable is defined in.
|
||||||
|
*/
|
||||||
|
private String definedIn;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
*
|
||||||
|
* @param provider The parent provider.
|
||||||
|
* @param name The name of this variable.
|
||||||
|
* @param type The type of this variable (e.g. "<code>int</code>",
|
||||||
|
* "<code>String</code>", etc.).
|
||||||
|
*/
|
||||||
|
public VariableCompletion(CompletionProvider provider, String name,
|
||||||
|
String type) {
|
||||||
|
super(provider, name);
|
||||||
|
this.type = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected void addDefinitionString(StringBuilder sb) {
|
||||||
|
sb.append("<html><b>").append(getDefinitionString()).append("</b>");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public String getDefinitionString() {
|
||||||
|
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
|
||||||
|
// Add the return type if applicable (C macros like NULL have no type).
|
||||||
|
if (type!=null) {
|
||||||
|
sb.append(type).append(' ');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the item being described's name.
|
||||||
|
sb.append(getName());
|
||||||
|
|
||||||
|
return sb.toString();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns where this variable is defined.
|
||||||
|
*
|
||||||
|
* @return Where this variable is defined.
|
||||||
|
* @see #setDefinedIn(String)
|
||||||
|
*/
|
||||||
|
public String getDefinedIn() {
|
||||||
|
return definedIn;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the name of this variable.
|
||||||
|
*
|
||||||
|
* @return The name.
|
||||||
|
*/
|
||||||
|
public String getName() {
|
||||||
|
return getReplacementText();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String getSummary() {
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
addDefinitionString(sb);
|
||||||
|
possiblyAddDescription(sb);
|
||||||
|
possiblyAddDefinedIn(sb);
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the tool tip text to display for mouse hovers over this
|
||||||
|
* completion.<p>
|
||||||
|
*
|
||||||
|
* Note that for this functionality to be enabled, a
|
||||||
|
* <tt>JTextComponent</tt> must be registered with the
|
||||||
|
* <tt>ToolTipManager</tt>, and the text component must know to search
|
||||||
|
* for this value. In the case of an
|
||||||
|
* <a href="http://fifesoft.com/rsyntaxtextarea">RSyntaxTextArea</a>, this
|
||||||
|
* can be done with a <tt>org.fife.ui.rtextarea.ToolTipSupplier</tt> that
|
||||||
|
* calls into
|
||||||
|
* {@link CompletionProvider#getCompletionsAt(JTextComponent, java.awt.Point)}.
|
||||||
|
*
|
||||||
|
* @return The tool tip text for this completion, or <code>null</code> if
|
||||||
|
* none.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String getToolTipText() {
|
||||||
|
return getDefinitionString();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the type of this variable.
|
||||||
|
*
|
||||||
|
* @return The type.
|
||||||
|
*/
|
||||||
|
public String getType() {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds some HTML describing where this variable is defined, if this
|
||||||
|
* information is known.
|
||||||
|
*
|
||||||
|
* @param sb The buffer to append to.
|
||||||
|
*/
|
||||||
|
protected void possiblyAddDefinedIn(StringBuilder sb) {
|
||||||
|
if (definedIn!=null) {
|
||||||
|
sb.append("<hr>Defined in:"); // TODO: Localize me
|
||||||
|
sb.append(" <em>").append(definedIn).append("</em>");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds the description text as HTML to a buffer, if a description is
|
||||||
|
* defined.
|
||||||
|
*
|
||||||
|
* @param sb The buffer to append to.
|
||||||
|
* @return Whether there was a description to add.
|
||||||
|
*/
|
||||||
|
protected boolean possiblyAddDescription(StringBuilder sb) {
|
||||||
|
if (getShortDescription()!=null) {
|
||||||
|
sb.append("<hr><br>");
|
||||||
|
sb.append(getShortDescription());
|
||||||
|
sb.append("<br><br><br>");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets where this variable is defined.
|
||||||
|
*
|
||||||
|
* @param definedIn Where this variable is defined.
|
||||||
|
* @see #getDefinedIn()
|
||||||
|
*/
|
||||||
|
public void setDefinedIn(String definedIn) {
|
||||||
|
this.definedIn = definedIn;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Overridden to return the name of the variable being completed.
|
||||||
|
*
|
||||||
|
* @return A string representation of this completion.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,288 @@
|
|||||||
|
/*
|
||||||
|
* 01/25/2009
|
||||||
|
*
|
||||||
|
* AbstractJFlexCTokenMaker.java - Base class for token makers that use curly
|
||||||
|
* braces to denote code blocks, such as C, C++, Java, Perl, etc.
|
||||||
|
*
|
||||||
|
* This library is distributed under a modified BSD license. See the included
|
||||||
|
* RSyntaxTextArea.License.txt file for details.
|
||||||
|
*/
|
||||||
|
package org.fife.ui.rsyntaxtextarea;
|
||||||
|
|
||||||
|
import java.awt.event.ActionEvent;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
import javax.swing.Action;
|
||||||
|
import javax.swing.UIManager;
|
||||||
|
import javax.swing.text.BadLocationException;
|
||||||
|
|
||||||
|
import org.fife.ui.rtextarea.RTextArea;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base class for JFlex-based token makers using C-style syntax. This class
|
||||||
|
* knows how to:
|
||||||
|
*
|
||||||
|
* <ul>
|
||||||
|
* <li>Auto-indent after opening braces and parens
|
||||||
|
* <li>Automatically close multi-line and documentation comments
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* @author Robert Futrell
|
||||||
|
* @version 1.0
|
||||||
|
*/
|
||||||
|
public abstract class AbstractJFlexCTokenMaker extends AbstractJFlexTokenMaker {
|
||||||
|
|
||||||
|
private final Action INSERT_BREAK_ACTION;
|
||||||
|
|
||||||
|
private static final Pattern MLC_PATTERN =
|
||||||
|
Pattern.compile("([ \\t]*)(/?[\\*]+)([ \\t]*)");
|
||||||
|
|
||||||
|
|
||||||
|
protected AbstractJFlexCTokenMaker() {
|
||||||
|
INSERT_BREAK_ACTION = createInsertBreakAction();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates and returns the action to use when the user inserts a newline.
|
||||||
|
* The default implementation intelligently closes multi-line comments.
|
||||||
|
* Subclasses can override.
|
||||||
|
*
|
||||||
|
* @return The action.
|
||||||
|
* @see #getInsertBreakAction()
|
||||||
|
*/
|
||||||
|
protected Action createInsertBreakAction() {
|
||||||
|
return new CStyleInsertBreakAction();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns <code>true</code> always as C-style languages use curly braces
|
||||||
|
* to denote code blocks.
|
||||||
|
*
|
||||||
|
* @return <code>true</code> always.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean getCurlyBracesDenoteCodeBlocks(int languageIndex) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an action to handle "insert break" key presses (i.e. Enter).
|
||||||
|
* An action is returned that handles newlines differently in multi-line
|
||||||
|
* comments.
|
||||||
|
*
|
||||||
|
* @return The action.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Action getInsertBreakAction() {
|
||||||
|
return INSERT_BREAK_ACTION;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean getMarkOccurrencesOfTokenType(int type) {
|
||||||
|
return type==Token.IDENTIFIER || type==Token.FUNCTION;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean getShouldIndentNextLineAfter(Token t) {
|
||||||
|
if (t!=null && t.length()==1) {
|
||||||
|
char ch = t.charAt(0);
|
||||||
|
return ch=='{' || ch=='(';
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether a given token is an internal token type that represents
|
||||||
|
* an MLC or documentation comment continuing on to the next line. This is
|
||||||
|
* done by languages such as JavaScript that are a little more verbose
|
||||||
|
* than necessary so that their code can be copy-and-pasted into other
|
||||||
|
* <code>TokenMaker</code>s that use them as nested languages (such as
|
||||||
|
* HTML, JSP, etc.).
|
||||||
|
*
|
||||||
|
* @param t The token to check. This cannot be <code>null</code>.
|
||||||
|
* @return Whether the token is an internal token representing the end of
|
||||||
|
* a line for an MLC/doc comment continuing on to the next line.
|
||||||
|
*/
|
||||||
|
private boolean isInternalEolTokenForMLCs(Token t) {
|
||||||
|
int type = t.getType();
|
||||||
|
if (type<0) {
|
||||||
|
type = getClosestStandardTokenTypeForInternalType(type);
|
||||||
|
return type==TokenTypes.COMMENT_MULTILINE ||
|
||||||
|
type==TokenTypes.COMMENT_DOCUMENTATION;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Action that knows how to special-case inserting a newline in a
|
||||||
|
* multi-line comment for languages like C and Java.
|
||||||
|
*/
|
||||||
|
protected class CStyleInsertBreakAction extends
|
||||||
|
RSyntaxTextAreaEditorKit.InsertBreakAction {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void actionPerformedImpl(ActionEvent e, RTextArea textArea) {
|
||||||
|
|
||||||
|
if (!textArea.isEditable() || !textArea.isEnabled()) {
|
||||||
|
UIManager.getLookAndFeel().provideErrorFeedback(textArea);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
RSyntaxTextArea rsta = (RSyntaxTextArea)getTextComponent(e);
|
||||||
|
RSyntaxDocument doc = (RSyntaxDocument)rsta.getDocument();
|
||||||
|
|
||||||
|
int line = textArea.getCaretLineNumber();
|
||||||
|
int type = doc.getLastTokenTypeOnLine(line);
|
||||||
|
if (type<0) {
|
||||||
|
type = doc.getClosestStandardTokenTypeForInternalType(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only in MLC's should we try this
|
||||||
|
if (type==Token.COMMENT_DOCUMENTATION ||
|
||||||
|
type==Token.COMMENT_MULTILINE) {
|
||||||
|
insertBreakInMLC(e, rsta, line);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
handleInsertBreak(rsta, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether the MLC token containing <code>offs</code> appears
|
||||||
|
* to have a "nested" comment (i.e., contains "<code>/*</code>"
|
||||||
|
* somewhere inside of it). This implies that it is likely a "new" MLC
|
||||||
|
* and needs to be closed. While not foolproof, this is usually good
|
||||||
|
* enough of a sign.
|
||||||
|
*
|
||||||
|
* @param textArea
|
||||||
|
* @param line
|
||||||
|
* @param offs
|
||||||
|
* @return Whether a comment appears to be nested inside this one.
|
||||||
|
*/
|
||||||
|
private boolean appearsNested(RSyntaxTextArea textArea,
|
||||||
|
int line, int offs) {
|
||||||
|
|
||||||
|
final int firstLine = line; // Remember the line we start at.
|
||||||
|
|
||||||
|
while (line<textArea.getLineCount()) {
|
||||||
|
Token t = textArea.getTokenListForLine(line);
|
||||||
|
int i = 0;
|
||||||
|
// If examining the first line, start at offs.
|
||||||
|
if (line++==firstLine) {
|
||||||
|
t = RSyntaxUtilities.getTokenAtOffset(t, offs);
|
||||||
|
if (t==null) { // offs was at end of the line
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
i = t.documentToToken(offs);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
i = t.getTextOffset();
|
||||||
|
}
|
||||||
|
int textOffset = t.getTextOffset();
|
||||||
|
while (i<textOffset+t.length()-1) {
|
||||||
|
if (t.charAt(i-textOffset)=='/' && t.charAt(i-textOffset+1)=='*') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
// If tokens come after this one on this line, our MLC ended.
|
||||||
|
if ((t=t.getNextToken())!=null && !isInternalEolTokenForMLCs(t)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true; // No match - MLC goes to the end of the file
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private void insertBreakInMLC(ActionEvent e, RSyntaxTextArea textArea,
|
||||||
|
int line) {
|
||||||
|
|
||||||
|
Matcher m = null;
|
||||||
|
int start = -1;
|
||||||
|
int end = -1;
|
||||||
|
String text = null;
|
||||||
|
try {
|
||||||
|
start = textArea.getLineStartOffset(line);
|
||||||
|
end = textArea.getLineEndOffset(line);
|
||||||
|
text = textArea.getText(start, end-start);
|
||||||
|
m = MLC_PATTERN.matcher(text);
|
||||||
|
} catch (BadLocationException ble) { // Never happens
|
||||||
|
UIManager.getLookAndFeel().provideErrorFeedback(textArea);
|
||||||
|
ble.printStackTrace();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m.lookingAt()) {
|
||||||
|
|
||||||
|
String leadingWS = m.group(1);
|
||||||
|
String mlcMarker = m.group(2);
|
||||||
|
|
||||||
|
// If the caret is "inside" any leading whitespace or MLC
|
||||||
|
// marker, move it to the end of the line.
|
||||||
|
int dot = textArea.getCaretPosition();
|
||||||
|
if (dot>=start &&
|
||||||
|
dot<start+leadingWS.length()+mlcMarker.length()) {
|
||||||
|
// If we're in the whitespace before the very start of the
|
||||||
|
// MLC though, just insert a normal newline
|
||||||
|
if (mlcMarker.charAt(0)=='/') {
|
||||||
|
handleInsertBreak(textArea, true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
textArea.setCaretPosition(end-1);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Ensure caret is at the "end" of any whitespace
|
||||||
|
// immediately after the '*', but before any possible
|
||||||
|
// non-whitespace chars.
|
||||||
|
boolean moved = false;
|
||||||
|
while (dot<end-1 &&
|
||||||
|
Character.isWhitespace(text.charAt(dot-start))) {
|
||||||
|
moved = true;
|
||||||
|
dot++;
|
||||||
|
}
|
||||||
|
if (moved) {
|
||||||
|
textArea.setCaretPosition(dot);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean firstMlcLine = mlcMarker.charAt(0)=='/';
|
||||||
|
boolean nested = appearsNested(textArea, line,
|
||||||
|
start+leadingWS.length()+2);
|
||||||
|
String header = leadingWS +
|
||||||
|
(firstMlcLine ? " * " : "*") +
|
||||||
|
m.group(3);
|
||||||
|
textArea.replaceSelection("\n" + header);
|
||||||
|
if (nested) {
|
||||||
|
dot = textArea.getCaretPosition(); // Has changed
|
||||||
|
textArea.insert("\n" + leadingWS + " */", dot);
|
||||||
|
textArea.setCaretPosition(dot);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
handleInsertBreak(textArea, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
/*
|
||||||
|
* 03/23/2005
|
||||||
|
*
|
||||||
|
* AbstractJFlexTokenMaker.java - Base class for token makers generated from
|
||||||
|
* programs such as JFlex.
|
||||||
|
*
|
||||||
|
* This library is distributed under a modified BSD license. See the included
|
||||||
|
* RSyntaxTextArea.License.txt file for details.
|
||||||
|
*/
|
||||||
|
package org.fife.ui.rsyntaxtextarea;
|
||||||
|
|
||||||
|
import javax.swing.text.Segment;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base class for JFlex-generated token makers. This class attempts to factor
|
||||||
|
* out all common code from these classes. Many methods <em>almost</em> could
|
||||||
|
* be factored out into this class, but cannot because they reference JFlex
|
||||||
|
* variables that we cannot access from this class.
|
||||||
|
*
|
||||||
|
* @author Robert Futrell
|
||||||
|
* @version 0.1
|
||||||
|
*/
|
||||||
|
public abstract class AbstractJFlexTokenMaker extends TokenMakerBase {
|
||||||
|
|
||||||
|
protected Segment s;
|
||||||
|
|
||||||
|
protected int start; // Just for states.
|
||||||
|
protected int offsetShift; // As parser always starts at 0, but our line doesn't.
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Declared here so we can define overloads that refer to this method.
|
||||||
|
*
|
||||||
|
* @param newState The new JFlex state to enter.
|
||||||
|
*/
|
||||||
|
public abstract void yybegin(int newState);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts a new JFlex state and changes the current language index.
|
||||||
|
*
|
||||||
|
* @param state The new JFlex state to enter.
|
||||||
|
* @param languageIndex The new language index.
|
||||||
|
*/
|
||||||
|
protected void yybegin(int state, int languageIndex) {
|
||||||
|
yybegin(state);
|
||||||
|
setLanguageIndex(languageIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
/*
|
||||||
|
* 11/07/2004
|
||||||
|
*
|
||||||
|
* AbstractTokenMaker.java - An abstract implementation of TokenMaker.
|
||||||
|
*
|
||||||
|
* This library is distributed under a modified BSD license. See the included
|
||||||
|
* RSyntaxTextArea.License.txt file for details.
|
||||||
|
*/
|
||||||
|
package org.fife.ui.rsyntaxtextarea;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An abstract implementation of the
|
||||||
|
* {@link org.fife.ui.rsyntaxtextarea.TokenMaker} interface. It should
|
||||||
|
* be overridden for every language for which you want to provide
|
||||||
|
* syntax highlighting.<p>
|
||||||
|
*
|
||||||
|
* @see Token
|
||||||
|
*
|
||||||
|
* @author Robert Futrell
|
||||||
|
* @version 0.2
|
||||||
|
*/
|
||||||
|
public abstract class AbstractTokenMaker extends TokenMakerBase {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hash table of words to highlight and what token type they are.
|
||||||
|
* The keys are the words to highlight, and their values are the
|
||||||
|
* token types, for example, <code>Token.RESERVED_WORD</code> or
|
||||||
|
* <code>Token.FUNCTION</code>.
|
||||||
|
*/
|
||||||
|
protected TokenMap wordsToHighlight;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
*/
|
||||||
|
public AbstractTokenMaker() {
|
||||||
|
wordsToHighlight = getWordsToHighlight();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the words to highlight for this programming language.
|
||||||
|
*
|
||||||
|
* @return A <code>TokenMap</code> containing the words to highlight for
|
||||||
|
* this programming language.
|
||||||
|
*/
|
||||||
|
public abstract TokenMap getWordsToHighlight();
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes the token last added from the linked list of tokens. The
|
||||||
|
* programmer should never have to call this directly; it can be called
|
||||||
|
* by subclasses of <code>TokenMaker</code> if necessary.
|
||||||
|
*/
|
||||||
|
public void removeLastToken() {
|
||||||
|
if (previousToken==null) {
|
||||||
|
firstToken = currentToken = null;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
currentToken = previousToken;
|
||||||
|
currentToken.setNextToken(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,134 @@
|
|||||||
|
/*
|
||||||
|
* 12/14/08
|
||||||
|
*
|
||||||
|
* AbstractTokenMakerFactory.java - Base class for TokenMaker implementations.
|
||||||
|
*
|
||||||
|
* This library is distributed under a modified BSD license. See the included
|
||||||
|
* RSyntaxTextArea.License.txt file for details.
|
||||||
|
*/
|
||||||
|
package org.fife.ui.rsyntaxtextarea;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base class for {@link TokenMakerFactory} implementations. A mapping from
|
||||||
|
* language keys to the names of {@link TokenMaker} classes is stored.
|
||||||
|
*
|
||||||
|
* @author Robert Futrell
|
||||||
|
* @version 1.0
|
||||||
|
*/
|
||||||
|
public abstract class AbstractTokenMakerFactory extends TokenMakerFactory {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A mapping from keys to the names of {@link TokenMaker} implementation
|
||||||
|
* class names. When {@link #getTokenMaker(String)} is called with a key
|
||||||
|
* defined in this map, a <code>TokenMaker</code> of the corresponding type
|
||||||
|
* is returned.
|
||||||
|
*/
|
||||||
|
private Map<String, Object> tokenMakerMap;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
*/
|
||||||
|
protected AbstractTokenMakerFactory() {
|
||||||
|
tokenMakerMap = new HashMap<String, Object>();
|
||||||
|
initTokenMakerMap();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a {@link TokenMaker} for the specified key.
|
||||||
|
*
|
||||||
|
* @param key The key.
|
||||||
|
* @return The corresponding <code>TokenMaker</code>, or <code>null</code>
|
||||||
|
* if none matches the specified key.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected TokenMaker getTokenMakerImpl(String key) {
|
||||||
|
TokenMakerCreator tmc = (TokenMakerCreator)tokenMakerMap.get(key);
|
||||||
|
if (tmc!=null) {
|
||||||
|
try {
|
||||||
|
return tmc.create();
|
||||||
|
} catch (RuntimeException re) { // FindBugs
|
||||||
|
throw re;
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Populates the mapping from keys to instances of
|
||||||
|
* <code>TokenMakerCreator</code>s. Subclasses should override this method
|
||||||
|
* and call one of the <code>putMapping</code> overloads to register
|
||||||
|
* {@link TokenMaker}s for syntax constants.
|
||||||
|
*
|
||||||
|
* @see #putMapping(String, String)
|
||||||
|
* @see #putMapping(String, String, ClassLoader)
|
||||||
|
*/
|
||||||
|
protected abstract void initTokenMakerMap();
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Set<String> keySet() {
|
||||||
|
return tokenMakerMap.keySet();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a mapping from a key to a <code>TokenMaker</code> implementation
|
||||||
|
* class name.
|
||||||
|
*
|
||||||
|
* @param key The key.
|
||||||
|
* @param className The <code>TokenMaker</code> class name.
|
||||||
|
* @see #putMapping(String, String, ClassLoader)
|
||||||
|
*/
|
||||||
|
public void putMapping(String key, String className) {
|
||||||
|
putMapping(key, className, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a mapping from a key to a <code>TokenMaker</code> implementation
|
||||||
|
* class name.
|
||||||
|
*
|
||||||
|
* @param key The key.
|
||||||
|
* @param className The <code>TokenMaker</code> class name.
|
||||||
|
* @param cl The class loader to use when loading the class.
|
||||||
|
* @see #putMapping(String, String)
|
||||||
|
*/
|
||||||
|
public void putMapping(String key, String className, ClassLoader cl) {
|
||||||
|
tokenMakerMap.put(key, new TokenMakerCreator(className, cl));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wrapper that handles the creation of TokenMaker instances.
|
||||||
|
*/
|
||||||
|
private static class TokenMakerCreator {
|
||||||
|
|
||||||
|
private String className;
|
||||||
|
private ClassLoader cl;
|
||||||
|
|
||||||
|
public TokenMakerCreator(String className, ClassLoader cl) {
|
||||||
|
this.className = className;
|
||||||
|
this.cl = cl!=null ? cl : getClass().getClassLoader();
|
||||||
|
}
|
||||||
|
|
||||||
|
public TokenMaker create() throws Exception {
|
||||||
|
return (TokenMaker)Class.forName(className, true, cl).newInstance();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
/*
|
||||||
|
* 02/06/2011
|
||||||
|
*
|
||||||
|
* ActiveLineRangeEvent.java - Notifies listeners of an "active line range"
|
||||||
|
* change in an RSyntaxTextArea.
|
||||||
|
*
|
||||||
|
* This library is distributed under a modified BSD license. See the included
|
||||||
|
* RSyntaxTextArea.License.txt file for details.
|
||||||
|
*/
|
||||||
|
package org.fife.ui.rsyntaxtextarea;
|
||||||
|
|
||||||
|
import java.util.EventObject;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The event fired by {@link RSyntaxTextArea}s when the active line range
|
||||||
|
* changes.
|
||||||
|
*
|
||||||
|
* @author Robert Futrell
|
||||||
|
* @version 1.0
|
||||||
|
*/
|
||||||
|
public class ActiveLineRangeEvent extends EventObject {
|
||||||
|
|
||||||
|
private int min;
|
||||||
|
private int max;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
*
|
||||||
|
* @param source The text area.
|
||||||
|
* @param min The first line in the active line range, or
|
||||||
|
* <code>-1</code> if the line range is being cleared.
|
||||||
|
* @param max The last line in the active line range, or
|
||||||
|
* <code>-1</code> if the line range is being cleared.
|
||||||
|
*/
|
||||||
|
public ActiveLineRangeEvent(RSyntaxTextArea source, int min, int max) {
|
||||||
|
super(source);
|
||||||
|
this.min = min;
|
||||||
|
this.max = max;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the last line in the active line range.
|
||||||
|
*
|
||||||
|
* @return The last line, or <code>-1</code> if the range is being
|
||||||
|
* cleared.
|
||||||
|
* @see #getMin()
|
||||||
|
*/
|
||||||
|
public int getMax() {
|
||||||
|
return max;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the first line in the active line range.
|
||||||
|
*
|
||||||
|
* @return The first line, or <code>-1</code> if the range is being
|
||||||
|
* cleared.
|
||||||
|
* @see #getMax()
|
||||||
|
*/
|
||||||
|
public int getMin() {
|
||||||
|
return min;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
/*
|
||||||
|
* 02/06/2011
|
||||||
|
*
|
||||||
|
* ActiveLineRangeListener.java - Listens for "active line range" changes
|
||||||
|
* in an RSyntaxTextArea.
|
||||||
|
*
|
||||||
|
* This library is distributed under a modified BSD license. See the included
|
||||||
|
* RSyntaxTextArea.License.txt file for details.
|
||||||
|
*/
|
||||||
|
package org.fife.ui.rsyntaxtextarea;
|
||||||
|
|
||||||
|
import java.util.EventListener;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Listens for "active line range" events from an <code>RSyntaxTextArea</code>.
|
||||||
|
* If a text area contains some semantic knowledge of the programming language
|
||||||
|
* being edited, it may broadcast {@link ActiveLineRangeEvent}s whenever the
|
||||||
|
* caret moves into a new "block" of code. Listeners can listen for these
|
||||||
|
* events and respond accordingly.<p>
|
||||||
|
*
|
||||||
|
* See the <code>RSTALanguageSupport</code> project at
|
||||||
|
* <a href="http://fifesoft.com">http://fifesoft.com</a> for some
|
||||||
|
* <code>LanguageSupport</code> implementations that may broadcast these
|
||||||
|
* events. Note that if an RSTA/LanguageSupport does not support broadcasting
|
||||||
|
* these events, the listener will simply never receive any notifications.
|
||||||
|
*
|
||||||
|
* @author Robert Futrell
|
||||||
|
* @version 1.0
|
||||||
|
*/
|
||||||
|
public interface ActiveLineRangeListener extends EventListener {
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called whenever the "active line range" changes.
|
||||||
|
*
|
||||||
|
* @param e Information about the line range change. If there is no longer
|
||||||
|
* an "active line range," the "minimum" and "maximum" line values
|
||||||
|
* should both be <code>-1</code>.
|
||||||
|
*/
|
||||||
|
void activeLineRangeChanged(ActiveLineRangeEvent e);
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,401 @@
|
|||||||
|
/*
|
||||||
|
* 02/21/2005
|
||||||
|
*
|
||||||
|
* CodeTemplateManager.java - manages code templates.
|
||||||
|
*
|
||||||
|
* This library is distributed under a modified BSD license. See the included
|
||||||
|
* RSyntaxTextArea.License.txt file for details.
|
||||||
|
*/
|
||||||
|
package org.fife.ui.rsyntaxtextarea;
|
||||||
|
|
||||||
|
import java.beans.XMLDecoder;
|
||||||
|
import java.beans.XMLEncoder;
|
||||||
|
import java.io.BufferedInputStream;
|
||||||
|
import java.io.BufferedOutputStream;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileFilter;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import javax.swing.text.BadLocationException;
|
||||||
|
import javax.swing.text.Document;
|
||||||
|
import javax.swing.text.Segment;
|
||||||
|
|
||||||
|
import org.fife.ui.rsyntaxtextarea.templates.CodeTemplate;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Manages "code templates."<p>
|
||||||
|
*
|
||||||
|
* All methods in this class are synchronized for thread safety, but as a
|
||||||
|
* best practice, you should probably only modify the templates known to a
|
||||||
|
* <code>CodeTemplateManager</code> on the EDT. Modifying a
|
||||||
|
* <code>CodeTemplate</code> retrieved from a <code>CodeTemplateManager</code>
|
||||||
|
* while <em>not</em> on the EDT could cause problems.<p>
|
||||||
|
*
|
||||||
|
* For more flexible boilerplate code insertion, consider using the
|
||||||
|
* <a href="http://javadoc.fifesoft.com/autocomplete/org/fife/ui/autocomplete/TemplateCompletion.html">
|
||||||
|
* TemplateCompletion class</a> in the
|
||||||
|
* <a href="https://github.com/bobbylight/AutoComplete">AutoComplete
|
||||||
|
* add-on library</a>.
|
||||||
|
*
|
||||||
|
* @author Robert Futrell
|
||||||
|
* @version 1.0
|
||||||
|
*/
|
||||||
|
public class CodeTemplateManager {
|
||||||
|
|
||||||
|
private int maxTemplateIDLength;
|
||||||
|
private List<CodeTemplate> templates;
|
||||||
|
|
||||||
|
private Segment s;
|
||||||
|
private TemplateComparator comparator;
|
||||||
|
private File directory;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
*/
|
||||||
|
public CodeTemplateManager() {
|
||||||
|
s = new Segment();
|
||||||
|
comparator = new TemplateComparator();
|
||||||
|
templates = new ArrayList<CodeTemplate>();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers the specified template with this template manager.
|
||||||
|
*
|
||||||
|
* @param template The template to register.
|
||||||
|
* @throws IllegalArgumentException If <code>template</code> is
|
||||||
|
* <code>null</code>.
|
||||||
|
* @see #removeTemplate(CodeTemplate)
|
||||||
|
* @see #removeTemplate(String)
|
||||||
|
*/
|
||||||
|
public synchronized void addTemplate(CodeTemplate template) {
|
||||||
|
if (template==null) {
|
||||||
|
throw new IllegalArgumentException("template cannot be null");
|
||||||
|
}
|
||||||
|
templates.add(template);
|
||||||
|
sortTemplates();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the template that should be inserted at the current caret
|
||||||
|
* position, assuming the trigger character was pressed.
|
||||||
|
*
|
||||||
|
* @param textArea The text area that's getting text inserted into it.
|
||||||
|
* @return A template that should be inserted, if appropriate, or
|
||||||
|
* <code>null</code> if no template should be inserted.
|
||||||
|
*/
|
||||||
|
public synchronized CodeTemplate getTemplate(RSyntaxTextArea textArea) {
|
||||||
|
int caretPos = textArea.getCaretPosition();
|
||||||
|
int charsToGet = Math.min(caretPos, maxTemplateIDLength);
|
||||||
|
try {
|
||||||
|
Document doc = textArea.getDocument();
|
||||||
|
doc.getText(caretPos-charsToGet, charsToGet, s);
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
int index = Collections.binarySearch(templates, s, comparator);
|
||||||
|
return index>=0 ? (CodeTemplate)templates.get(index) : null;
|
||||||
|
} catch (BadLocationException ble) {
|
||||||
|
ble.printStackTrace();
|
||||||
|
throw new InternalError("Error in CodeTemplateManager");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the number of templates this manager knows about.
|
||||||
|
*
|
||||||
|
* @return The template count.
|
||||||
|
*/
|
||||||
|
public synchronized int getTemplateCount() {
|
||||||
|
return templates.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the templates currently available.
|
||||||
|
*
|
||||||
|
* @return The templates available.
|
||||||
|
*/
|
||||||
|
public synchronized CodeTemplate[] getTemplates() {
|
||||||
|
CodeTemplate[] temp = new CodeTemplate[templates.size()];
|
||||||
|
return templates.toArray(temp);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether the specified character is a valid character for a
|
||||||
|
* <code>CodeTemplate</code> id.
|
||||||
|
*
|
||||||
|
* @param ch The character to check.
|
||||||
|
* @return Whether the character is a valid template character.
|
||||||
|
*/
|
||||||
|
public static final boolean isValidChar(char ch) {
|
||||||
|
return RSyntaxUtilities.isLetterOrDigit(ch) || ch=='_';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the specified code template.
|
||||||
|
*
|
||||||
|
* @param template The template to remove.
|
||||||
|
* @return <code>true</code> if the template was removed, <code>false</code>
|
||||||
|
* if the template was not in this template manager.
|
||||||
|
* @throws IllegalArgumentException If <code>template</code> is
|
||||||
|
* <code>null</code>.
|
||||||
|
* @see #removeTemplate(String)
|
||||||
|
* @see #addTemplate(CodeTemplate)
|
||||||
|
*/
|
||||||
|
public synchronized boolean removeTemplate(CodeTemplate template) {
|
||||||
|
|
||||||
|
if (template==null) {
|
||||||
|
throw new IllegalArgumentException("template cannot be null");
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Do a binary search
|
||||||
|
return templates.remove(template);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the code template with the specified id.
|
||||||
|
*
|
||||||
|
* @param id The id to check for.
|
||||||
|
* @return The code template that was removed, or <code>null</code> if
|
||||||
|
* there was no template with the specified ID.
|
||||||
|
* @throws IllegalArgumentException If <code>id</code> is <code>null</code>.
|
||||||
|
* @see #removeTemplate(CodeTemplate)
|
||||||
|
* @see #addTemplate(CodeTemplate)
|
||||||
|
*/
|
||||||
|
public synchronized CodeTemplate removeTemplate(String id) {
|
||||||
|
|
||||||
|
if (id==null) {
|
||||||
|
throw new IllegalArgumentException("id cannot be null");
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Do a binary search
|
||||||
|
for (Iterator<CodeTemplate> i=templates.iterator(); i.hasNext();) {
|
||||||
|
CodeTemplate template = i.next();
|
||||||
|
if (id.equals(template.getID())) {
|
||||||
|
i.remove();
|
||||||
|
return template;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Replaces the current set of available templates with the ones
|
||||||
|
* specified.
|
||||||
|
*
|
||||||
|
* @param newTemplates The new set of templates. Note that we will
|
||||||
|
* be taking a shallow copy of these and sorting them.
|
||||||
|
*/
|
||||||
|
public synchronized void replaceTemplates(CodeTemplate[] newTemplates) {
|
||||||
|
templates.clear();
|
||||||
|
if (newTemplates!=null) {
|
||||||
|
for (int i=0; i<newTemplates.length; i++) {
|
||||||
|
templates.add(newTemplates[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sortTemplates(); // Also recomputes maxTemplateIDLength.
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Saves all templates as XML files in the current template directory.
|
||||||
|
*
|
||||||
|
* @return Whether or not the save was successful.
|
||||||
|
*/
|
||||||
|
public synchronized boolean saveTemplates() {
|
||||||
|
|
||||||
|
if (templates==null) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (directory==null || !directory.isDirectory()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Blow away all old XML files to start anew, as some might be from
|
||||||
|
// templates we're removed from the template manager.
|
||||||
|
File[] oldXMLFiles = directory.listFiles(new XMLFileFilter());
|
||||||
|
if (oldXMLFiles==null) {
|
||||||
|
return false; // Either an IOException or it isn't a directory.
|
||||||
|
}
|
||||||
|
int count = oldXMLFiles.length;
|
||||||
|
for (int i=0; i<count; i++) {
|
||||||
|
/*boolean deleted = */oldXMLFiles[i].delete();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save all current templates as XML.
|
||||||
|
boolean wasSuccessful = true;
|
||||||
|
for (CodeTemplate template : templates) {
|
||||||
|
File xmlFile = new File(directory, template.getID() + ".xml");
|
||||||
|
try {
|
||||||
|
XMLEncoder e = new XMLEncoder(new BufferedOutputStream(
|
||||||
|
new FileOutputStream(xmlFile)));
|
||||||
|
e.writeObject(template);
|
||||||
|
e.close();
|
||||||
|
} catch (IOException ioe) {
|
||||||
|
ioe.printStackTrace();
|
||||||
|
wasSuccessful = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return wasSuccessful;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the directory in which to look for templates. Calling this
|
||||||
|
* method adds any new templates found in the specified directory to
|
||||||
|
* the templates already registered.
|
||||||
|
*
|
||||||
|
* @param dir The new directory in which to look for templates.
|
||||||
|
* @return The new number of templates in this template manager, or
|
||||||
|
* <code>-1</code> if the specified directory does not exist.
|
||||||
|
*/
|
||||||
|
public synchronized int setTemplateDirectory(File dir) {
|
||||||
|
|
||||||
|
if (dir!=null && dir.isDirectory()) {
|
||||||
|
|
||||||
|
this.directory = dir;
|
||||||
|
|
||||||
|
File[] files = dir.listFiles(new XMLFileFilter());
|
||||||
|
int newCount = files==null ? 0 : files.length;
|
||||||
|
int oldCount = templates.size();
|
||||||
|
|
||||||
|
List<CodeTemplate> temp =
|
||||||
|
new ArrayList<CodeTemplate>(oldCount+newCount);
|
||||||
|
temp.addAll(templates);
|
||||||
|
|
||||||
|
for (int i=0; i<newCount; i++) {
|
||||||
|
try {
|
||||||
|
XMLDecoder d = new XMLDecoder(new BufferedInputStream(
|
||||||
|
new FileInputStream(files[i])));
|
||||||
|
Object obj = d.readObject();
|
||||||
|
if (!(obj instanceof CodeTemplate)) {
|
||||||
|
d.close();
|
||||||
|
throw new IOException("Not a CodeTemplate: " +
|
||||||
|
files[i].getAbsolutePath());
|
||||||
|
}
|
||||||
|
temp.add((CodeTemplate)obj);
|
||||||
|
d.close();
|
||||||
|
} catch (/*IO, NoSuchElement*/Exception e) {
|
||||||
|
// NoSuchElementException can be thrown when reading
|
||||||
|
// an XML file not in the format expected by XMLDecoder.
|
||||||
|
// (e.g. CodeTemplates in an old format).
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
templates = temp;
|
||||||
|
sortTemplates();
|
||||||
|
|
||||||
|
return getTemplateCount();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes any null entries in the current set of templates (if
|
||||||
|
* any), sorts the remaining templates, and computes the new
|
||||||
|
* maximum template ID length.
|
||||||
|
*/
|
||||||
|
private synchronized void sortTemplates() {
|
||||||
|
|
||||||
|
// Get the maximum length of a template ID.
|
||||||
|
maxTemplateIDLength = 0;
|
||||||
|
|
||||||
|
// Remove any null entries (should only happen because of
|
||||||
|
// IOExceptions, etc. when loading from files), and sort
|
||||||
|
// the remaining list.
|
||||||
|
for (Iterator<CodeTemplate> i=templates.iterator(); i.hasNext();) {
|
||||||
|
CodeTemplate temp = i.next();
|
||||||
|
if (temp==null || temp.getID()==null) {
|
||||||
|
i.remove();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
maxTemplateIDLength = Math.max(maxTemplateIDLength,
|
||||||
|
temp.getID().length());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Collections.sort(templates);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A comparator that takes a <code>CodeTemplate</code> as its first
|
||||||
|
* parameter and a <code>Segment</code> as its second, and knows
|
||||||
|
* to compare the template's ID to the segment's text.
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("rawtypes")
|
||||||
|
private static class TemplateComparator implements Comparator, Serializable{
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int compare(Object template, Object segment) {
|
||||||
|
|
||||||
|
// Get template start index (0) and length.
|
||||||
|
CodeTemplate t = (CodeTemplate)template;
|
||||||
|
final char[] templateArray = t.getID().toCharArray();
|
||||||
|
int i = 0;
|
||||||
|
int len1 = templateArray.length;
|
||||||
|
|
||||||
|
// Find "token" part of segment and get its offset and length.
|
||||||
|
Segment s = (Segment)segment;
|
||||||
|
char[] segArray = s.array;
|
||||||
|
int len2 = s.count;
|
||||||
|
int j = s.offset + len2 - 1;
|
||||||
|
while (j>=s.offset && isValidChar(segArray[j])) {
|
||||||
|
j--;
|
||||||
|
}
|
||||||
|
j++;
|
||||||
|
int segShift = j - s.offset;
|
||||||
|
len2 -= segShift;
|
||||||
|
|
||||||
|
int n = Math.min(len1, len2);
|
||||||
|
while (n-- != 0) {
|
||||||
|
char c1 = templateArray[i++];
|
||||||
|
char c2 = segArray[j++];
|
||||||
|
if (c1 != c2) {
|
||||||
|
return c1 - c2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return len1 - len2;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A file filter that accepts only XML files.
|
||||||
|
*/
|
||||||
|
private static class XMLFileFilter implements FileFilter {
|
||||||
|
@Override
|
||||||
|
public boolean accept(File f) {
|
||||||
|
return f.getName().toLowerCase().endsWith(".xml");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,112 @@
|
|||||||
|
/*
|
||||||
|
* 03/09/2013
|
||||||
|
*
|
||||||
|
* DefaultOccurrenceMarker - Marks occurrences of the current token for most
|
||||||
|
* languages.
|
||||||
|
*
|
||||||
|
* This library is distributed under a modified BSD license. See the included
|
||||||
|
* RSyntaxTextArea.License.txt file for details.
|
||||||
|
*/
|
||||||
|
package org.fife.ui.rsyntaxtextarea;
|
||||||
|
|
||||||
|
import javax.swing.text.BadLocationException;
|
||||||
|
import javax.swing.text.Caret;
|
||||||
|
|
||||||
|
import org.fife.ui.rtextarea.SmartHighlightPainter;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The default implementation of {@link OccurrenceMarker}. It goes through
|
||||||
|
* the document and marks all instances of the specified token.
|
||||||
|
*
|
||||||
|
* @author Robert Futrell
|
||||||
|
* @version 1.0
|
||||||
|
*/
|
||||||
|
class DefaultOccurrenceMarker implements OccurrenceMarker {
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Token getTokenToMark(RSyntaxTextArea textArea) {
|
||||||
|
|
||||||
|
// Get the token at the caret position.
|
||||||
|
int line = textArea.getCaretLineNumber();
|
||||||
|
Token tokenList = textArea.getTokenListForLine(line);
|
||||||
|
Caret c = textArea.getCaret();
|
||||||
|
int dot = c.getDot();
|
||||||
|
|
||||||
|
Token t = RSyntaxUtilities.getTokenAtOffset(tokenList, dot);
|
||||||
|
if (t==null /* EOL */ || !isValidType(textArea, t) ||
|
||||||
|
RSyntaxUtilities.isNonWordChar(t)) {
|
||||||
|
// Try to the "left" of the caret.
|
||||||
|
dot--;
|
||||||
|
try {
|
||||||
|
if (dot>=textArea.getLineStartOffset(line)) {
|
||||||
|
t = RSyntaxUtilities.getTokenAtOffset(tokenList, dot);
|
||||||
|
}
|
||||||
|
} catch (BadLocationException ble) {
|
||||||
|
ble.printStackTrace(); // Never happens
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return t;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean isValidType(RSyntaxTextArea textArea, Token t) {
|
||||||
|
return textArea.getMarkOccurrencesOfTokenType(t.getType());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void markOccurrences(RSyntaxDocument doc, Token t,
|
||||||
|
RSyntaxTextAreaHighlighter h, SmartHighlightPainter p) {
|
||||||
|
markOccurrencesOfToken(doc, t, h, p);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Highlights all instances of tokens identical to <code>t</code> in the
|
||||||
|
* specified document.
|
||||||
|
*
|
||||||
|
* @param doc The document.
|
||||||
|
* @param t The document whose relevant occurrences should be marked.
|
||||||
|
* @param h The highlighter to add the highlights to.
|
||||||
|
* @param p The painter for the highlights.
|
||||||
|
*/
|
||||||
|
public static final void markOccurrencesOfToken(RSyntaxDocument doc,
|
||||||
|
Token t, RSyntaxTextAreaHighlighter h, SmartHighlightPainter p) {
|
||||||
|
|
||||||
|
char[] lexeme = t.getLexeme().toCharArray();
|
||||||
|
int type = t.getType();
|
||||||
|
int lineCount = doc.getDefaultRootElement().getElementCount();
|
||||||
|
|
||||||
|
for (int i=0; i<lineCount; i++) {
|
||||||
|
Token temp = doc.getTokenListForLine(i);
|
||||||
|
while (temp!=null && temp.isPaintable()) {
|
||||||
|
if (temp.is(type, lexeme)) {
|
||||||
|
try {
|
||||||
|
int end = temp.getEndOffset();
|
||||||
|
h.addMarkedOccurrenceHighlight(temp.getOffset(),end,p);
|
||||||
|
} catch (BadLocationException ble) {
|
||||||
|
ble.printStackTrace(); // Never happens
|
||||||
|
}
|
||||||
|
}
|
||||||
|
temp = temp.getNextToken();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,143 @@
|
|||||||
|
/*
|
||||||
|
* 10/28/2004
|
||||||
|
*
|
||||||
|
* DefaultTokenFactory.java - Default token factory.
|
||||||
|
*
|
||||||
|
* This library is distributed under a modified BSD license. See the included
|
||||||
|
* RSyntaxTextArea.License.txt file for details.
|
||||||
|
*/
|
||||||
|
package org.fife.ui.rsyntaxtextarea;
|
||||||
|
|
||||||
|
import javax.swing.text.Segment;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class generates tokens for a {@link TokenMaker}. This class is here
|
||||||
|
* because it reuses tokens when they aren't needed anymore to prevent
|
||||||
|
* This class doesn't actually create new tokens every time
|
||||||
|
* <code>createToken</code> is called. Instead, it internally keeps a stack of
|
||||||
|
* available already-created tokens. When more tokens are needed to properly
|
||||||
|
* display a line, more tokens are added to the available stack. This saves
|
||||||
|
* from needless repetitive memory allocation. However, it makes it IMPERATIVE
|
||||||
|
* that users call <code>resetTokenList</code> when creating a new token list so
|
||||||
|
* that the token maker can keep an accurate list of available tokens.<p>
|
||||||
|
*
|
||||||
|
* NOTE: This class should only be used by {@link TokenMaker}; nobody else
|
||||||
|
* needs it!
|
||||||
|
*
|
||||||
|
* @author Robert Futrell
|
||||||
|
* @version 0.1
|
||||||
|
*/
|
||||||
|
class DefaultTokenFactory implements TokenFactory {
|
||||||
|
|
||||||
|
private int size;
|
||||||
|
private int increment;
|
||||||
|
private TokenImpl[] tokenList;
|
||||||
|
private int currentFreeToken;
|
||||||
|
|
||||||
|
protected static final int DEFAULT_START_SIZE = 30;
|
||||||
|
protected static final int DEFAULT_INCREMENT = 10;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
*/
|
||||||
|
DefaultTokenFactory() {
|
||||||
|
this(DEFAULT_START_SIZE, DEFAULT_INCREMENT);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
*
|
||||||
|
* @param size The initial number of tokens in this factory.
|
||||||
|
* @param increment How many tokens to increment by when the stack gets
|
||||||
|
* empty.
|
||||||
|
*/
|
||||||
|
DefaultTokenFactory(int size, int increment) {
|
||||||
|
|
||||||
|
this.size = size;
|
||||||
|
this.increment = increment;
|
||||||
|
this.currentFreeToken = 0;
|
||||||
|
|
||||||
|
// Give us some tokens to initially work with.
|
||||||
|
tokenList = new TokenImpl[size];
|
||||||
|
for (int i=0; i<size; i++) {
|
||||||
|
tokenList[i] = new TokenImpl();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds tokens to the internal token list. This is called whenever a
|
||||||
|
* request is made and no more tokens are available.
|
||||||
|
*/
|
||||||
|
private void augmentTokenList() {
|
||||||
|
TokenImpl[] temp = new TokenImpl[size + increment];
|
||||||
|
System.arraycopy(tokenList,0, temp,0, size);
|
||||||
|
size += increment;
|
||||||
|
tokenList = temp;
|
||||||
|
for (int i=0; i<increment; i++) {
|
||||||
|
tokenList[size-i-1] = new TokenImpl();
|
||||||
|
}
|
||||||
|
//System.err.println("... size up to: " + size);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public TokenImpl createToken() {
|
||||||
|
TokenImpl token = tokenList[currentFreeToken];
|
||||||
|
token.text = null;
|
||||||
|
token.setType(Token.NULL);
|
||||||
|
token.setOffset(-1);
|
||||||
|
token.setNextToken(null);
|
||||||
|
currentFreeToken++;
|
||||||
|
if (currentFreeToken==size) {
|
||||||
|
augmentTokenList();
|
||||||
|
}
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public TokenImpl createToken(final Segment line, final int beg,
|
||||||
|
final int end, final int startOffset, final int type) {
|
||||||
|
return createToken(line.array, beg,end, startOffset, type);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public TokenImpl createToken(final char[] line, final int beg,
|
||||||
|
final int end, final int startOffset, final int type) {
|
||||||
|
TokenImpl token = tokenList[currentFreeToken];
|
||||||
|
token.set(line, beg,end, startOffset, type);
|
||||||
|
currentFreeToken++;
|
||||||
|
if (currentFreeToken==size) {
|
||||||
|
augmentTokenList();
|
||||||
|
}
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resets the state of this token maker. This method should be called
|
||||||
|
* by the <code>TokenMaker</code> every time a token list is generated for
|
||||||
|
* a new line so the tokens can be reused.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void resetAllTokens() {
|
||||||
|
currentFreeToken = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
/*
|
||||||
|
* 12/14/2008
|
||||||
|
*
|
||||||
|
* DefaultTokenMakerFactory.java - The default TokenMaker factory.
|
||||||
|
*
|
||||||
|
* This library is distributed under a modified BSD license. See the included
|
||||||
|
* RSyntaxTextArea.License.txt file for details.
|
||||||
|
*/
|
||||||
|
package org.fife.ui.rsyntaxtextarea;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The default implementation of <code>TokenMakerFactory</code>. This factory
|
||||||
|
* can create {@link TokenMaker}s for all languages known to
|
||||||
|
* {@link RSyntaxTextArea}.
|
||||||
|
*
|
||||||
|
* @author Robert Futrell
|
||||||
|
* @version 1.0
|
||||||
|
*/
|
||||||
|
class DefaultTokenMakerFactory extends AbstractTokenMakerFactory
|
||||||
|
implements SyntaxConstants {
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected void initTokenMakerMap() {
|
||||||
|
|
||||||
|
String pkg = "org.fife.ui.rsyntaxtextarea.modes.";
|
||||||
|
|
||||||
|
putMapping(SYNTAX_STYLE_NONE, pkg + "PlainTextTokenMaker");
|
||||||
|
putMapping(SYNTAX_STYLE_ACTIONSCRIPT, pkg + "ActionScriptTokenMaker");
|
||||||
|
putMapping(SYNTAX_STYLE_ASSEMBLER_X86, pkg + "AssemblerX86TokenMaker");
|
||||||
|
putMapping(SYNTAX_STYLE_BBCODE, pkg + "BBCodeTokenMaker");
|
||||||
|
putMapping(SYNTAX_STYLE_C, pkg + "CTokenMaker");
|
||||||
|
putMapping(SYNTAX_STYLE_CLOJURE, pkg + "ClojureTokenMaker");
|
||||||
|
putMapping(SYNTAX_STYLE_CPLUSPLUS, pkg + "CPlusPlusTokenMaker");
|
||||||
|
putMapping(SYNTAX_STYLE_CSHARP, pkg + "CSharpTokenMaker");
|
||||||
|
putMapping(SYNTAX_STYLE_CSS, pkg + "CSSTokenMaker");
|
||||||
|
putMapping(SYNTAX_STYLE_D, pkg + "DTokenMaker");
|
||||||
|
putMapping(SYNTAX_STYLE_DART, pkg + "DartTokenMaker");
|
||||||
|
putMapping(SYNTAX_STYLE_DELPHI, pkg + "DelphiTokenMaker");
|
||||||
|
putMapping(SYNTAX_STYLE_DOCKERFILE, pkg + "DockerTokenMaker");
|
||||||
|
putMapping(SYNTAX_STYLE_DTD, pkg + "DtdTokenMaker");
|
||||||
|
putMapping(SYNTAX_STYLE_FORTRAN, pkg + "FortranTokenMaker");
|
||||||
|
putMapping(SYNTAX_STYLE_GROOVY, pkg + "GroovyTokenMaker");
|
||||||
|
putMapping(SYNTAX_STYLE_HOSTS, pkg + "HostsTokenMaker");
|
||||||
|
putMapping(SYNTAX_STYLE_HTACCESS, pkg + "HtaccessTokenMaker");
|
||||||
|
putMapping(SYNTAX_STYLE_HTML, pkg + "HTMLTokenMaker");
|
||||||
|
putMapping(SYNTAX_STYLE_INI, pkg + "IniTokenMaker");
|
||||||
|
putMapping(SYNTAX_STYLE_JAVA, pkg + "JavaTokenMaker");
|
||||||
|
putMapping(SYNTAX_STYLE_JAVASCRIPT, pkg + "JavaScriptTokenMaker");
|
||||||
|
putMapping(SYNTAX_STYLE_JSON_WITH_COMMENTS, pkg + "JshintrcTokenMaker");
|
||||||
|
putMapping(SYNTAX_STYLE_JSON, pkg + "JsonTokenMaker");
|
||||||
|
putMapping(SYNTAX_STYLE_JSP, pkg + "JSPTokenMaker");
|
||||||
|
putMapping(SYNTAX_STYLE_LATEX, pkg + "LatexTokenMaker");
|
||||||
|
putMapping(SYNTAX_STYLE_LESS, pkg + "LessTokenMaker");
|
||||||
|
putMapping(SYNTAX_STYLE_LISP, pkg + "LispTokenMaker");
|
||||||
|
putMapping(SYNTAX_STYLE_LUA, pkg + "LuaTokenMaker");
|
||||||
|
putMapping(SYNTAX_STYLE_MAKEFILE, pkg + "MakefileTokenMaker");
|
||||||
|
putMapping(SYNTAX_STYLE_MXML, pkg + "MxmlTokenMaker");
|
||||||
|
putMapping(SYNTAX_STYLE_NSIS, pkg + "NSISTokenMaker");
|
||||||
|
putMapping(SYNTAX_STYLE_PERL, pkg + "PerlTokenMaker");
|
||||||
|
putMapping(SYNTAX_STYLE_PHP, pkg + "PHPTokenMaker");
|
||||||
|
putMapping(SYNTAX_STYLE_PROPERTIES_FILE,pkg + "PropertiesFileTokenMaker");
|
||||||
|
putMapping(SYNTAX_STYLE_PYTHON, pkg + "PythonTokenMaker");
|
||||||
|
putMapping(SYNTAX_STYLE_RUBY, pkg + "RubyTokenMaker");
|
||||||
|
putMapping(SYNTAX_STYLE_SAS, pkg + "SASTokenMaker");
|
||||||
|
putMapping(SYNTAX_STYLE_SCALA, pkg + "ScalaTokenMaker");
|
||||||
|
putMapping(SYNTAX_STYLE_SQL, pkg + "SQLTokenMaker");
|
||||||
|
putMapping(SYNTAX_STYLE_TCL, pkg + "TclTokenMaker");
|
||||||
|
putMapping(SYNTAX_STYLE_TYPESCRIPT, pkg + "TypeScriptTokenMaker");
|
||||||
|
putMapping(SYNTAX_STYLE_UNIX_SHELL, pkg + "UnixShellTokenMaker");
|
||||||
|
putMapping(SYNTAX_STYLE_VISUAL_BASIC, pkg + "VisualBasicTokenMaker");
|
||||||
|
putMapping(SYNTAX_STYLE_WINDOWS_BATCH, pkg + "WindowsBatchTokenMaker");
|
||||||
|
putMapping(SYNTAX_STYLE_XML, pkg + "XMLTokenMaker");
|
||||||
|
putMapping(SYNTAX_STYLE_YAML, pkg + "YamlTokenMaker");
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,279 @@
|
|||||||
|
/*
|
||||||
|
* 03/16/2013
|
||||||
|
*
|
||||||
|
* DefaultTokenPainter - Standard implementation of a token painter.
|
||||||
|
*
|
||||||
|
* This library is distributed under a modified BSD license. See the included
|
||||||
|
* RSyntaxTextArea.License.txt file for details.
|
||||||
|
*/
|
||||||
|
package org.fife.ui.rsyntaxtextarea;
|
||||||
|
|
||||||
|
import java.awt.Color;
|
||||||
|
import java.awt.FontMetrics;
|
||||||
|
import java.awt.Graphics2D;
|
||||||
|
import java.awt.geom.Rectangle2D;
|
||||||
|
|
||||||
|
import javax.swing.text.TabExpander;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Standard implementation of a token painter.
|
||||||
|
*
|
||||||
|
* @author Robert Futrell
|
||||||
|
* @version 1.0
|
||||||
|
*/
|
||||||
|
class DefaultTokenPainter implements TokenPainter {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rectangle used for filling token backgrounds.
|
||||||
|
*/
|
||||||
|
private Rectangle2D.Float bgRect;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Micro-optimization; buffer used to compute tab width. If the width is
|
||||||
|
* correct it's not re-allocated, to prevent lots of very small garbage.
|
||||||
|
* Only used when painting tab lines.
|
||||||
|
*/
|
||||||
|
private static char[] tabBuf;
|
||||||
|
|
||||||
|
|
||||||
|
DefaultTokenPainter() {
|
||||||
|
bgRect = new Rectangle2D.Float();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public final float paint(Token token, Graphics2D g, float x, float y,
|
||||||
|
RSyntaxTextArea host, TabExpander e) {
|
||||||
|
return paint(token, g, x,y, host, e, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public float paint(Token token, Graphics2D g, float x, float y,
|
||||||
|
RSyntaxTextArea host, TabExpander e, float clipStart) {
|
||||||
|
return paintImpl(token, g, x, y, host, e, clipStart, false, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public float paint(Token token, Graphics2D g, float x, float y,
|
||||||
|
RSyntaxTextArea host, TabExpander e, float clipStart,
|
||||||
|
boolean paintBG) {
|
||||||
|
return paintImpl(token, g, x, y, host, e, clipStart, !paintBG, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Paints the background of a token.
|
||||||
|
*
|
||||||
|
* @param x The x-coordinate of the token.
|
||||||
|
* @param y The y-coordinate of the token.
|
||||||
|
* @param width The width of the token (actually, the width of the part of
|
||||||
|
* the token to paint).
|
||||||
|
* @param height The height of the token.
|
||||||
|
* @param g The graphics context with which to paint.
|
||||||
|
* @param fontAscent The ascent of the token's font.
|
||||||
|
* @param host The text area.
|
||||||
|
* @param color The color with which to paint.
|
||||||
|
*/
|
||||||
|
protected void paintBackground(float x, float y, float width, float height,
|
||||||
|
Graphics2D g, int fontAscent, RSyntaxTextArea host,
|
||||||
|
Color color) {
|
||||||
|
g.setColor(color);
|
||||||
|
bgRect.setRect(x,y-fontAscent, width,height);
|
||||||
|
//g.fill(bgRect);
|
||||||
|
g.fillRect((int)x, (int)(y-fontAscent), (int)width, (int)height);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Does the dirty-work of actually painting the token.
|
||||||
|
*/
|
||||||
|
protected float paintImpl(Token token, Graphics2D g, float x, float y,
|
||||||
|
RSyntaxTextArea host, TabExpander e, float clipStart,
|
||||||
|
boolean selected, boolean useSTC) {
|
||||||
|
|
||||||
|
int origX = (int)x;
|
||||||
|
int textOffs = token.getTextOffset();
|
||||||
|
char[] text = token.getTextArray();
|
||||||
|
int end = textOffs + token.length();
|
||||||
|
float nextX = x;
|
||||||
|
int flushLen = 0;
|
||||||
|
int flushIndex = textOffs;
|
||||||
|
Color fg = useSTC ? host.getSelectedTextColor() :
|
||||||
|
host.getForegroundForToken(token);
|
||||||
|
Color bg = selected ? null : host.getBackgroundForToken(token);
|
||||||
|
g.setFont(host.getFontForTokenType(token.getType()));
|
||||||
|
FontMetrics fm = host.getFontMetricsForTokenType(token.getType());
|
||||||
|
|
||||||
|
for (int i=textOffs; i<end; i++) {
|
||||||
|
switch (text[i]) {
|
||||||
|
case '\t':
|
||||||
|
nextX = e.nextTabStop(
|
||||||
|
x + fm.charsWidth(text, flushIndex,flushLen), 0);
|
||||||
|
if (bg!=null) {
|
||||||
|
paintBackground(x,y, nextX-x,fm.getHeight(),
|
||||||
|
g, fm.getAscent(), host, bg);
|
||||||
|
}
|
||||||
|
if (flushLen > 0) {
|
||||||
|
g.setColor(fg);
|
||||||
|
g.drawChars(text, flushIndex, flushLen, (int)x,(int)y);
|
||||||
|
flushLen = 0;
|
||||||
|
}
|
||||||
|
flushIndex = i + 1;
|
||||||
|
x = nextX;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
flushLen += 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
nextX = x+fm.charsWidth(text, flushIndex,flushLen);
|
||||||
|
java.awt.Rectangle r = host.getMatchRectangle();
|
||||||
|
|
||||||
|
if (flushLen>0 && nextX>=clipStart) {
|
||||||
|
if (bg!=null) {
|
||||||
|
paintBackground(x,y, nextX-x,fm.getHeight(),
|
||||||
|
g, fm.getAscent(), host, bg);
|
||||||
|
if (token.length()==1 && r!=null && r.x==x) {
|
||||||
|
((RSyntaxTextAreaUI)host.getUI()).paintMatchedBracketImpl(
|
||||||
|
g, host, r);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
g.setColor(fg);
|
||||||
|
g.drawChars(text, flushIndex, flushLen, (int)x,(int)y);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (host.getUnderlineForToken(token)) {
|
||||||
|
g.setColor(fg);
|
||||||
|
int y2 = (int)(y+1);
|
||||||
|
g.drawLine(origX,y2, (int)nextX,y2);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't check if it's whitespace - some TokenMakers may return types
|
||||||
|
// other than Token.WHITESPACE for spaces (such as Token.IDENTIFIER).
|
||||||
|
// This also allows us to paint tab lines for MLC's.
|
||||||
|
if (host.getPaintTabLines() && origX==host.getMargin().left) {// && isWhitespace()) {
|
||||||
|
paintTabLines(token, origX, (int)y, (int)nextX, g, e, host);
|
||||||
|
}
|
||||||
|
|
||||||
|
return nextX;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public float paintSelected(Token token, Graphics2D g, float x, float y,
|
||||||
|
RSyntaxTextArea host, TabExpander e, boolean useSTC) {
|
||||||
|
return paintSelected(token, g, x, y, host, e, 0, useSTC);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public float paintSelected(Token token, Graphics2D g, float x, float y,
|
||||||
|
RSyntaxTextArea host, TabExpander e, float clipStart,
|
||||||
|
boolean useSTC) {
|
||||||
|
return paintImpl(token, g, x, y, host, e, clipStart, true, useSTC);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Paints dotted "tab" lines; that is, lines that show where your caret
|
||||||
|
* would go to on the line if you hit "tab". This visual effect is usually
|
||||||
|
* done in the leading whitespace token(s) of lines.
|
||||||
|
*
|
||||||
|
* @param token The token to render.
|
||||||
|
* @param x The starting x-offset of this token. It is assumed that this
|
||||||
|
* is the left margin of the text area (may be non-zero due to
|
||||||
|
* insets), since tab lines are only painted for leading whitespace.
|
||||||
|
* @param y The baseline where this token was painted.
|
||||||
|
* @param endX The ending x-offset of this token.
|
||||||
|
* @param g The graphics context.
|
||||||
|
* @param e Used to expand tabs.
|
||||||
|
* @param host The text area.
|
||||||
|
*/
|
||||||
|
protected void paintTabLines(Token token, int x, int y, int endX,
|
||||||
|
Graphics2D g, TabExpander e, RSyntaxTextArea host) {
|
||||||
|
|
||||||
|
// We allow tab lines to be painted in more than just Token.WHITESPACE,
|
||||||
|
// i.e. for MLC's and Token.IDENTIFIERS (for TokenMakers that return
|
||||||
|
// whitespace as identifiers for performance). But we only paint tab
|
||||||
|
// lines for the leading whitespace in the token. So, if this isn't a
|
||||||
|
// WHITESPACE token, figure out the leading whitespace's length.
|
||||||
|
if (token.getType()!=Token.WHITESPACE) {
|
||||||
|
int offs = 0;
|
||||||
|
for (; offs<token.length(); offs++) {
|
||||||
|
if (!RSyntaxUtilities.isWhitespace(token.charAt(offs))) {
|
||||||
|
break; // MLC text, etc.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (offs<2) { // Must be at least two spaces to see tab line
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
//endX = x + (int)getWidthUpTo(offs, host, e, x);
|
||||||
|
endX = (int)token.getWidthUpTo(offs, host, e, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the length of a tab.
|
||||||
|
FontMetrics fm = host.getFontMetricsForTokenType(token.getType());
|
||||||
|
int tabSize = host.getTabSize();
|
||||||
|
if (tabBuf==null || tabBuf.length<tabSize) {
|
||||||
|
tabBuf = new char[tabSize];
|
||||||
|
for (int i=0; i<tabSize; i++) {
|
||||||
|
tabBuf[i] = ' ';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Note different token types (MLC's, whitespace) could possibly be
|
||||||
|
// using different fonts, which means we can't cache the actual width
|
||||||
|
// of a tab as it may be different per-token-type. We could keep a
|
||||||
|
// per-token-type cache, but we'd have to clear it whenever they
|
||||||
|
// modified token styles.
|
||||||
|
int tabW = fm.charsWidth(tabBuf, 0, tabSize);
|
||||||
|
|
||||||
|
// Draw any tab lines. Here we're assuming that "x" is the left
|
||||||
|
// margin of the editor.
|
||||||
|
g.setColor(host.getTabLineColor());
|
||||||
|
int x0 = x + tabW;
|
||||||
|
int y0 = y - fm.getAscent();
|
||||||
|
if ((y0&1)>0) {
|
||||||
|
// Only paint on even y-pixels to prevent doubling up between lines
|
||||||
|
y0++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Go to endX (inclusive) if this token is last token in the line
|
||||||
|
Token next = token.getNextToken();
|
||||||
|
if (next==null || !next.isPaintable()) {
|
||||||
|
endX++;
|
||||||
|
}
|
||||||
|
while (x0<endX) {
|
||||||
|
int y1 = y0;
|
||||||
|
int y2 = y0 + host.getLineHeight();
|
||||||
|
while (y1<y2) {
|
||||||
|
g.drawLine(x0, y1, x0, y1);
|
||||||
|
y1 += 2;
|
||||||
|
}
|
||||||
|
//g.drawLine(x0,y0, x0,y0+host.getLineHeight());
|
||||||
|
x0 += tabW;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,169 @@
|
|||||||
|
/*
|
||||||
|
* 08/11/2009
|
||||||
|
*
|
||||||
|
* DocumentRange.java - A range of text in a document.
|
||||||
|
*
|
||||||
|
* This library is distributed under a modified BSD license. See the included
|
||||||
|
* RSyntaxTextArea.License.txt file for details.
|
||||||
|
*/
|
||||||
|
package org.fife.ui.rsyntaxtextarea;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A range of text in a document.
|
||||||
|
*
|
||||||
|
* @author Robert Futrell
|
||||||
|
* @version 1.0
|
||||||
|
*/
|
||||||
|
public class DocumentRange implements Comparable<DocumentRange> {
|
||||||
|
|
||||||
|
private int startOffs;
|
||||||
|
private int endOffs;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
*
|
||||||
|
* @param startOffs The starting offset in the document, inclusive.
|
||||||
|
* @param endOffs The ending offset in the document, exclusive.
|
||||||
|
* @throws IllegalArgumentException If <code>endOffs</code> is less than
|
||||||
|
* <code>startOffs</code>, or either argument is less than zero.
|
||||||
|
*/
|
||||||
|
public DocumentRange(int startOffs, int endOffs) {
|
||||||
|
set(startOffs, endOffs);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compares this document range to another.
|
||||||
|
*
|
||||||
|
* @param other Another document range.
|
||||||
|
* @return How the two should be sorted relative to each other.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public int compareTo(DocumentRange other) {
|
||||||
|
if (other==null) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
int diff = startOffs - other.startOffs;
|
||||||
|
if (diff!=0) {
|
||||||
|
return diff;
|
||||||
|
}
|
||||||
|
return endOffs - other.endOffs;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether this document range is equal to another one.
|
||||||
|
*
|
||||||
|
* @param other Another object, presumably a document range.
|
||||||
|
* @return Whether <code>other</code> is also a document range, and equal
|
||||||
|
* to this one.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object other) {
|
||||||
|
if (other==this) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (other instanceof DocumentRange) {
|
||||||
|
return this.compareTo((DocumentRange)other)==0;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the end offset of the range.
|
||||||
|
*
|
||||||
|
* @return The end offset.
|
||||||
|
* @see #getStartOffset()
|
||||||
|
*/
|
||||||
|
public int getEndOffset() {
|
||||||
|
return endOffs;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the starting offset of the range.
|
||||||
|
*
|
||||||
|
* @return The starting offset.
|
||||||
|
* @see #getEndOffset()
|
||||||
|
*/
|
||||||
|
public int getStartOffset() {
|
||||||
|
return startOffs;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Overridden simply as a best practice, since {@link #equals(Object)} is
|
||||||
|
* overridden.
|
||||||
|
*
|
||||||
|
* @return The hash code for this object.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return startOffs + endOffs;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether this document range has zero length. This can happen,
|
||||||
|
* for example, with regex searches of forms like
|
||||||
|
* <code>"foo|"</code>, where the right-hand sub-expression matches empty
|
||||||
|
* strings.
|
||||||
|
*
|
||||||
|
* @return Whether this document range has zero length.
|
||||||
|
*/
|
||||||
|
public boolean isZeroLength() {
|
||||||
|
return startOffs == endOffs;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the document range.
|
||||||
|
*
|
||||||
|
* @param start The new start value, inclusive.
|
||||||
|
* @param end The new end value, exclusive.
|
||||||
|
* @throws IllegalArgumentException If <code>end</code> is less than
|
||||||
|
* <code>start</code>, or either argument is less than zero.
|
||||||
|
*/
|
||||||
|
public void set(int start, int end) {
|
||||||
|
if (start<0 || end<0) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"start and end must be >= 0 (" + start + "-" + end + ")");
|
||||||
|
}
|
||||||
|
if (end<start) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"'end' cannot be less than 'start' (" +
|
||||||
|
start + "-" + end + ")");
|
||||||
|
}
|
||||||
|
this.startOffs = start;
|
||||||
|
this.endOffs = end;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a string representation of this object.
|
||||||
|
*
|
||||||
|
* @return A string representation of this object.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "[DocumentRange: " + startOffs + "-" + endOffs + "]";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Translates this document range by a given amount.
|
||||||
|
*
|
||||||
|
* @param amount The amount to translate this range by.
|
||||||
|
* @return This (modified) range.
|
||||||
|
*/
|
||||||
|
public DocumentRange translate(int amount) {
|
||||||
|
startOffs += amount;
|
||||||
|
endOffs += amount;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,924 @@
|
|||||||
|
/*
|
||||||
|
* 08/10/2009
|
||||||
|
*
|
||||||
|
* ErrorStrip.java - A component that can visually show Parser messages (syntax
|
||||||
|
* errors, etc.) in an RSyntaxTextArea.
|
||||||
|
*
|
||||||
|
* This library is distributed under a modified BSD license. See the included
|
||||||
|
* RSyntaxTextArea.License.txt file for details.
|
||||||
|
*/
|
||||||
|
package org.fife.ui.rsyntaxtextarea;
|
||||||
|
|
||||||
|
import java.awt.Color;
|
||||||
|
import java.awt.Component;
|
||||||
|
import java.awt.Cursor;
|
||||||
|
import java.awt.Dimension;
|
||||||
|
import java.awt.Graphics;
|
||||||
|
import java.awt.Rectangle;
|
||||||
|
import java.awt.event.MouseAdapter;
|
||||||
|
import java.awt.event.MouseEvent;
|
||||||
|
import java.beans.PropertyChangeEvent;
|
||||||
|
import java.beans.PropertyChangeListener;
|
||||||
|
import java.text.MessageFormat;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.ResourceBundle;
|
||||||
|
|
||||||
|
import javax.swing.JComponent;
|
||||||
|
import javax.swing.JPanel;
|
||||||
|
import javax.swing.ToolTipManager;
|
||||||
|
import javax.swing.UIManager;
|
||||||
|
import javax.swing.event.CaretEvent;
|
||||||
|
import javax.swing.event.CaretListener;
|
||||||
|
import javax.swing.text.BadLocationException;
|
||||||
|
|
||||||
|
import org.fife.ui.rsyntaxtextarea.parser.Parser;
|
||||||
|
import org.fife.ui.rsyntaxtextarea.parser.ParserNotice;
|
||||||
|
import org.fife.ui.rsyntaxtextarea.parser.TaskTagParser.TaskNotice;
|
||||||
|
import org.fife.ui.rtextarea.RTextArea;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A component to sit alongside an {@link RSyntaxTextArea} that displays
|
||||||
|
* colored markers for locations of interest (parser errors, marked
|
||||||
|
* occurrences, etc.).<p>
|
||||||
|
*
|
||||||
|
* <code>ErrorStrip</code>s display <code>ParserNotice</code>s from
|
||||||
|
* {@link Parser}s. Currently, the only way to get lines flagged in this
|
||||||
|
* component is to register a <code>Parser</code> on an RSyntaxTextArea and
|
||||||
|
* return <code>ParserNotice</code>s for each line to display an icon for.
|
||||||
|
* The severity of each notice must be at least the threshold set by
|
||||||
|
* {@link #setLevelThreshold(org.fife.ui.rsyntaxtextarea.parser.ParserNotice.Level)}
|
||||||
|
* to be displayed in this error strip. The default threshold is
|
||||||
|
* {@link org.fife.ui.rsyntaxtextarea.parser.ParserNotice.Level#WARNING}.<p>
|
||||||
|
*
|
||||||
|
* An <code>ErrorStrip</code> can be added to a UI like so:
|
||||||
|
* <pre>
|
||||||
|
* textArea = createTextArea();
|
||||||
|
* textArea.addParser(new MyParser(textArea)); // Identifies lines to display
|
||||||
|
* scrollPane = new RTextScrollPane(textArea, true);
|
||||||
|
* ErrorStrip es = new ErrorStrip(textArea);
|
||||||
|
* JPanel temp = new JPanel(new BorderLayout());
|
||||||
|
* temp.add(scrollPane);
|
||||||
|
* temp.add(es, BorderLayout.LINE_END);
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* @author Robert Futrell
|
||||||
|
* @version 0.5
|
||||||
|
*/
|
||||||
|
// Possible improvements:
|
||||||
|
// 1. Handle marked occurrence changes & "mark all" changes separately from
|
||||||
|
// parser changes. For each property change, call a method that removes
|
||||||
|
// the notices being reloaded from the Markers (removing any Markers that
|
||||||
|
// are now "empty").
|
||||||
|
//
|
||||||
|
public class ErrorStrip extends JPanel {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The text area.
|
||||||
|
*/
|
||||||
|
private RSyntaxTextArea textArea;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Listens for events in this component.
|
||||||
|
*/
|
||||||
|
private transient Listener listener;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether "marked occurrences" in the text area should be shown in this
|
||||||
|
* error strip.
|
||||||
|
*/
|
||||||
|
private boolean showMarkedOccurrences;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether markers for "mark all" highlights should be shown in this
|
||||||
|
* error strip.
|
||||||
|
*/
|
||||||
|
private boolean showMarkAll;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mapping of colors to brighter colors. This is kept to prevent
|
||||||
|
* unnecessary creation of the same Colors over and over.
|
||||||
|
*/
|
||||||
|
private Map<Color, Color> brighterColors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Only notices of this severity (or worse) will be displayed in this
|
||||||
|
* error strip.
|
||||||
|
*/
|
||||||
|
private ParserNotice.Level levelThreshold;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the caret marker's location should be rendered.
|
||||||
|
*/
|
||||||
|
private boolean followCaret;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The color to use for the caret marker.
|
||||||
|
*/
|
||||||
|
private Color caretMarkerColor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Where we paint the caret marker.
|
||||||
|
*/
|
||||||
|
private int caretLineY;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The last location of the caret marker.
|
||||||
|
*/
|
||||||
|
private int lastLineY;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates the tool tips for markers in this error strip.
|
||||||
|
*/
|
||||||
|
private transient ErrorStripMarkerToolTipProvider markerToolTipProvider;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The preferred width of this component.
|
||||||
|
*/
|
||||||
|
private static final int PREFERRED_WIDTH = 14;
|
||||||
|
|
||||||
|
private static final ResourceBundle MSG = ResourceBundle.getBundle(
|
||||||
|
"org.fife.ui.rsyntaxtextarea.ErrorStrip");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
*
|
||||||
|
* @param textArea The text area we are examining.
|
||||||
|
*/
|
||||||
|
public ErrorStrip(RSyntaxTextArea textArea) {
|
||||||
|
this.textArea = textArea;
|
||||||
|
listener = new Listener();
|
||||||
|
ToolTipManager.sharedInstance().registerComponent(this);
|
||||||
|
setLayout(null); // Manually layout Markers as they can overlap
|
||||||
|
addMouseListener(listener);
|
||||||
|
setShowMarkedOccurrences(true);
|
||||||
|
setShowMarkAll(true);
|
||||||
|
setLevelThreshold(ParserNotice.Level.WARNING);
|
||||||
|
setFollowCaret(true);
|
||||||
|
setCaretMarkerColor(Color.BLACK);
|
||||||
|
setMarkerToolTipProvider(null); // Install default
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Overridden so we only start listening for parser notices when this
|
||||||
|
* component (and presumably the text area) are visible.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void addNotify() {
|
||||||
|
super.addNotify();
|
||||||
|
textArea.addCaretListener(listener);
|
||||||
|
textArea.addPropertyChangeListener(
|
||||||
|
RSyntaxTextArea.PARSER_NOTICES_PROPERTY, listener);
|
||||||
|
textArea.addPropertyChangeListener(
|
||||||
|
RSyntaxTextArea.MARK_OCCURRENCES_PROPERTY, listener);
|
||||||
|
textArea.addPropertyChangeListener(
|
||||||
|
RSyntaxTextArea.MARKED_OCCURRENCES_CHANGED_PROPERTY, listener);
|
||||||
|
textArea.addPropertyChangeListener(
|
||||||
|
RSyntaxTextArea.MARK_ALL_OCCURRENCES_CHANGED_PROPERTY, listener);
|
||||||
|
refreshMarkers();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Manually manages layout since this component uses no layout manager.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void doLayout() {
|
||||||
|
for (int i=0; i<getComponentCount(); i++) {
|
||||||
|
Marker m = (Marker)getComponent(i);
|
||||||
|
m.updateLocation();
|
||||||
|
}
|
||||||
|
listener.caretUpdate(null); // Force recalculation of caret line pos
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a "brighter" color.
|
||||||
|
*
|
||||||
|
* @param c The color.
|
||||||
|
* @return A brighter color.
|
||||||
|
*/
|
||||||
|
private Color getBrighterColor(Color c) {
|
||||||
|
if (brighterColors==null) {
|
||||||
|
brighterColors = new HashMap<Color, Color>(5); // Usually small
|
||||||
|
}
|
||||||
|
Color brighter = brighterColors.get(c);
|
||||||
|
if (brighter==null) {
|
||||||
|
// Don't use c.brighter() as it doesn't work well for blue, and
|
||||||
|
// also doesn't return something brighter "enough."
|
||||||
|
int r = possiblyBrighter(c.getRed());
|
||||||
|
int g = possiblyBrighter(c.getGreen());
|
||||||
|
int b = possiblyBrighter(c.getBlue());
|
||||||
|
brighter = new Color(r, g, b);
|
||||||
|
brighterColors.put(c, brighter);
|
||||||
|
}
|
||||||
|
return brighter;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* returns the color to use when painting the caret marker.
|
||||||
|
*
|
||||||
|
* @return The caret marker color.
|
||||||
|
* @see #setCaretMarkerColor(Color)
|
||||||
|
*/
|
||||||
|
public Color getCaretMarkerColor() {
|
||||||
|
return caretMarkerColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether the caret's position should be drawn.
|
||||||
|
*
|
||||||
|
* @return Whether the caret's position should be drawn.
|
||||||
|
* @see #setFollowCaret(boolean)
|
||||||
|
*/
|
||||||
|
public boolean getFollowCaret() {
|
||||||
|
return followCaret;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Dimension getPreferredSize() {
|
||||||
|
int height = textArea.getPreferredScrollableViewportSize().height;
|
||||||
|
return new Dimension(PREFERRED_WIDTH, height);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the minimum severity a parser notice must be for it to be
|
||||||
|
* displayed in this error strip. This will be one of the constants
|
||||||
|
* defined in the <code>ParserNotice</code> class.
|
||||||
|
*
|
||||||
|
* @return The minimum severity.
|
||||||
|
* @see #setLevelThreshold(org.fife.ui.rsyntaxtextarea.parser.ParserNotice.Level)
|
||||||
|
*/
|
||||||
|
public ParserNotice.Level getLevelThreshold() {
|
||||||
|
return levelThreshold;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether "mark all" highlights are shown in this error strip.
|
||||||
|
*
|
||||||
|
* @return Whether markers are shown for "mark all" highlights.
|
||||||
|
* @see #setShowMarkAll(boolean)
|
||||||
|
*/
|
||||||
|
public boolean getShowMarkAll() {
|
||||||
|
return showMarkAll;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether marked occurrences are shown in this error strip.
|
||||||
|
*
|
||||||
|
* @return Whether marked occurrences are shown.
|
||||||
|
* @see #setShowMarkedOccurrences(boolean)
|
||||||
|
*/
|
||||||
|
public boolean getShowMarkedOccurrences() {
|
||||||
|
return showMarkedOccurrences;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String getToolTipText(MouseEvent e) {
|
||||||
|
String text = null;
|
||||||
|
int line = yToLine(e.getY());
|
||||||
|
if (line>-1) {
|
||||||
|
text = MSG.getString("Line");
|
||||||
|
text = MessageFormat.format(text, Integer.valueOf(line+1));
|
||||||
|
}
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the y-offset in this component corresponding to a line in the
|
||||||
|
* text component.
|
||||||
|
*
|
||||||
|
* @param line The line.
|
||||||
|
* @return The y-offset.
|
||||||
|
* @see #yToLine(int)
|
||||||
|
*/
|
||||||
|
private int lineToY(int line) {
|
||||||
|
int h = textArea.getVisibleRect().height;
|
||||||
|
float lineCount = textArea.getLineCount();
|
||||||
|
return (int)(((line-1)/(lineCount-1)) * (h-2));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Overridden to (possibly) draw the caret's position.
|
||||||
|
*
|
||||||
|
* @param g The graphics context.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected void paintComponent(Graphics g) {
|
||||||
|
super.paintComponent(g);
|
||||||
|
if (caretLineY>-1) {
|
||||||
|
g.setColor(getCaretMarkerColor());
|
||||||
|
g.fillRect(0, caretLineY, getWidth(), 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a possibly brighter component for a color.
|
||||||
|
*
|
||||||
|
* @param i An RGB component for a color (0-255).
|
||||||
|
* @return A possibly brighter value for the component.
|
||||||
|
*/
|
||||||
|
private static int possiblyBrighter(int i) {
|
||||||
|
if (i<255) {
|
||||||
|
i += (int)((255-i)*0.8f);
|
||||||
|
}
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Refreshes the markers displayed in this error strip.
|
||||||
|
*/
|
||||||
|
private void refreshMarkers() {
|
||||||
|
|
||||||
|
removeAll(); // listener is removed in Marker.removeNotify()
|
||||||
|
Map<Integer, Marker> markerMap = new HashMap<Integer, Marker>();
|
||||||
|
|
||||||
|
List<ParserNotice> notices = textArea.getParserNotices();
|
||||||
|
for (ParserNotice notice : notices) {
|
||||||
|
if (notice.getLevel().isEqualToOrWorseThan(levelThreshold) ||
|
||||||
|
(notice instanceof TaskNotice)) {
|
||||||
|
Integer key = Integer.valueOf(notice.getLine());
|
||||||
|
Marker m = markerMap.get(key);
|
||||||
|
if (m==null) {
|
||||||
|
m = new Marker(notice);
|
||||||
|
m.addMouseListener(listener);
|
||||||
|
markerMap.put(key, m);
|
||||||
|
add(m);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
m.addNotice(notice);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (getShowMarkedOccurrences() && textArea.getMarkOccurrences()) {
|
||||||
|
List<DocumentRange> occurrences = textArea.getMarkedOccurrences();
|
||||||
|
addMarkersForRanges(occurrences, markerMap, textArea.getMarkOccurrencesColor());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (getShowMarkAll() /*&& textArea.getMarkAll()*/) {
|
||||||
|
Color markAllColor = textArea.getMarkAllHighlightColor();
|
||||||
|
List<DocumentRange> ranges = textArea.getMarkAllHighlightRanges();
|
||||||
|
addMarkersForRanges(ranges, markerMap, markAllColor);
|
||||||
|
}
|
||||||
|
|
||||||
|
revalidate();
|
||||||
|
repaint();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds markers for a list of ranges in the document.
|
||||||
|
*
|
||||||
|
* @param ranges The list of ranges in the document.
|
||||||
|
* @param markerMap A mapping from line number to <code>Marker</code>.
|
||||||
|
* @param color The color to use for the markers.
|
||||||
|
*/
|
||||||
|
private void addMarkersForRanges(List<DocumentRange> ranges,
|
||||||
|
Map<Integer, Marker> markerMap, Color color) {
|
||||||
|
for (DocumentRange range : ranges) {
|
||||||
|
int line = 0;
|
||||||
|
try {
|
||||||
|
line = textArea.getLineOfOffset(range.getStartOffset());
|
||||||
|
} catch (BadLocationException ble) { // Never happens
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
ParserNotice notice = new MarkedOccurrenceNotice(range, color);
|
||||||
|
Integer key = Integer.valueOf(line);
|
||||||
|
Marker m = markerMap.get(key);
|
||||||
|
if (m==null) {
|
||||||
|
m = new Marker(notice);
|
||||||
|
m.addMouseListener(listener);
|
||||||
|
markerMap.put(key, m);
|
||||||
|
add(m);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (!m.containsMarkedOccurence()) {
|
||||||
|
m.addNotice(notice);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void removeNotify() {
|
||||||
|
super.removeNotify();
|
||||||
|
textArea.removeCaretListener(listener);
|
||||||
|
textArea.removePropertyChangeListener(
|
||||||
|
RSyntaxTextArea.PARSER_NOTICES_PROPERTY, listener);
|
||||||
|
textArea.removePropertyChangeListener(
|
||||||
|
RSyntaxTextArea.MARK_OCCURRENCES_PROPERTY, listener);
|
||||||
|
textArea.removePropertyChangeListener(
|
||||||
|
RSyntaxTextArea.MARKED_OCCURRENCES_CHANGED_PROPERTY, listener);
|
||||||
|
textArea.removePropertyChangeListener(
|
||||||
|
RSyntaxTextArea.MARK_ALL_OCCURRENCES_CHANGED_PROPERTY, listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the color to use when painting the caret marker.
|
||||||
|
*
|
||||||
|
* @param color The new caret marker color.
|
||||||
|
* @see #getCaretMarkerColor()
|
||||||
|
*/
|
||||||
|
public void setCaretMarkerColor(Color color) {
|
||||||
|
if (color!=null) {
|
||||||
|
caretMarkerColor = color;
|
||||||
|
listener.caretUpdate(null); // Force repaint
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Toggles whether the caret's current location should be drawn.
|
||||||
|
*
|
||||||
|
* @param follow Whether the caret's current location should be followed.
|
||||||
|
* @see #getFollowCaret()
|
||||||
|
*/
|
||||||
|
public void setFollowCaret(boolean follow) {
|
||||||
|
if (followCaret!=follow) {
|
||||||
|
if (followCaret) {
|
||||||
|
repaint(0,caretLineY, getWidth(),2); // Erase
|
||||||
|
}
|
||||||
|
caretLineY = -1;
|
||||||
|
lastLineY = -1;
|
||||||
|
followCaret = follow;
|
||||||
|
listener.caretUpdate(null); // Possibly repaint
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the minimum severity a parser notice must be for it to be displayed
|
||||||
|
* in this error strip. This should be one of the constants defined in
|
||||||
|
* the <code>ParserNotice</code> class. The default value is
|
||||||
|
* {@link org.fife.ui.rsyntaxtextarea.parser.ParserNotice.Level#WARNING}.
|
||||||
|
*
|
||||||
|
* @param level The new severity threshold.
|
||||||
|
* @see #getLevelThreshold()
|
||||||
|
* @see ParserNotice
|
||||||
|
*/
|
||||||
|
public void setLevelThreshold(ParserNotice.Level level) {
|
||||||
|
levelThreshold = level;
|
||||||
|
if (isDisplayable()) {
|
||||||
|
refreshMarkers();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the provider of tool tips for markers in this error strip.
|
||||||
|
* Applications can use this method to control the content and format of
|
||||||
|
* the tool tip descriptions of line markers.
|
||||||
|
*
|
||||||
|
* @param provider The provider. If this is <code>null</code>, a default
|
||||||
|
* implementation will be used.
|
||||||
|
*/
|
||||||
|
public void setMarkerToolTipProvider(ErrorStripMarkerToolTipProvider provider) {
|
||||||
|
markerToolTipProvider = provider != null ? provider :
|
||||||
|
new DefaultErrorStripMarkerToolTipProvider();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets whether "mark all" highlights are shown in this error strip.
|
||||||
|
*
|
||||||
|
* @param show Whether to show markers for "mark all" highlights.
|
||||||
|
* @see #getShowMarkAll()
|
||||||
|
*/
|
||||||
|
public void setShowMarkAll(boolean show) {
|
||||||
|
if (show!=showMarkAll) {
|
||||||
|
showMarkAll = show;
|
||||||
|
if (isDisplayable()) { // Skip this when we're first created
|
||||||
|
refreshMarkers();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets whether marked occurrences are shown in this error strip.
|
||||||
|
*
|
||||||
|
* @param show Whether to show marked occurrences.
|
||||||
|
* @see #getShowMarkedOccurrences()
|
||||||
|
*/
|
||||||
|
public void setShowMarkedOccurrences(boolean show) {
|
||||||
|
if (show!=showMarkedOccurrences) {
|
||||||
|
showMarkedOccurrences = show;
|
||||||
|
if (isDisplayable()) { // Skip this when we're first created
|
||||||
|
refreshMarkers();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the line in the text area corresponding to a y-offset in this
|
||||||
|
* component.
|
||||||
|
*
|
||||||
|
* @param y The y-offset.
|
||||||
|
* @return The line.
|
||||||
|
* @see #lineToY(int)
|
||||||
|
*/
|
||||||
|
private int yToLine(int y) {
|
||||||
|
int line = -1;
|
||||||
|
int h = textArea.getVisibleRect().height;
|
||||||
|
if (y<h) {
|
||||||
|
float at = y/(float)h;
|
||||||
|
line = Math.round((textArea.getLineCount()-1)*at);
|
||||||
|
}
|
||||||
|
return line;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The default implementation of the provider of tool tips for markers in
|
||||||
|
* an error strip.
|
||||||
|
*
|
||||||
|
* @author predi
|
||||||
|
*/
|
||||||
|
private static class DefaultErrorStripMarkerToolTipProvider
|
||||||
|
implements ErrorStripMarkerToolTipProvider {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getToolTipText(List<ParserNotice> notices) {
|
||||||
|
|
||||||
|
String text = null;
|
||||||
|
|
||||||
|
if (notices.size()==1) {
|
||||||
|
text = notices.get(0).getMessage();
|
||||||
|
}
|
||||||
|
else { // > 1
|
||||||
|
StringBuilder sb = new StringBuilder("<html>");
|
||||||
|
sb.append(MSG.getString("MultipleMarkers"));
|
||||||
|
sb.append("<br>");
|
||||||
|
for (int i=0; i<notices.size(); i++) {
|
||||||
|
ParserNotice pn = notices.get(i);
|
||||||
|
sb.append(" - ");
|
||||||
|
sb.append(pn.getMessage());
|
||||||
|
sb.append("<br>");
|
||||||
|
}
|
||||||
|
text = sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
return text;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns tool tip text for the markers in an {@link ErrorStrip} that
|
||||||
|
* denote one or more parser notices.
|
||||||
|
*
|
||||||
|
* @author predi
|
||||||
|
*/
|
||||||
|
public interface ErrorStripMarkerToolTipProvider {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the tool tip text for a marker in an <code>ErrorStrip</code>
|
||||||
|
* that denotes a given list of parser notices.
|
||||||
|
*
|
||||||
|
* @param notices The list of parser notices.
|
||||||
|
* @return The tool tip text. This may be HTML. Returning
|
||||||
|
* <code>null</code> will result in no tool tip being displayed.
|
||||||
|
*/
|
||||||
|
String getToolTipText(List<ParserNotice> notices);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Listens for events in the error strip and its markers.
|
||||||
|
*/
|
||||||
|
private class Listener extends MouseAdapter
|
||||||
|
implements PropertyChangeListener, CaretListener {
|
||||||
|
|
||||||
|
private Rectangle visibleRect = new Rectangle();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void caretUpdate(CaretEvent e) {
|
||||||
|
if (getFollowCaret()) {
|
||||||
|
int line = textArea.getCaretLineNumber();
|
||||||
|
float percent = line / (float)(textArea.getLineCount()-1);
|
||||||
|
textArea.computeVisibleRect(visibleRect);
|
||||||
|
caretLineY = (int)(visibleRect.height*percent);
|
||||||
|
if (caretLineY!=lastLineY) {
|
||||||
|
repaint(0,lastLineY, getWidth(), 2); // Erase old position
|
||||||
|
repaint(0,caretLineY, getWidth(), 2);
|
||||||
|
lastLineY = caretLineY;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void mouseClicked(MouseEvent e) {
|
||||||
|
|
||||||
|
Component source = (Component)e.getSource();
|
||||||
|
if (source instanceof Marker) {
|
||||||
|
((Marker)source).mouseClicked(e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int line = yToLine(e.getY());
|
||||||
|
if (line>-1) {
|
||||||
|
try {
|
||||||
|
int offs = textArea.getLineStartOffset(line);
|
||||||
|
textArea.setCaretPosition(offs);
|
||||||
|
} catch (BadLocationException ble) { // Never happens
|
||||||
|
UIManager.getLookAndFeel().provideErrorFeedback(textArea);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void propertyChange(PropertyChangeEvent e) {
|
||||||
|
|
||||||
|
String propName = e.getPropertyName();
|
||||||
|
|
||||||
|
// If they change whether marked occurrences are visible in editor
|
||||||
|
if (RSyntaxTextArea.MARK_OCCURRENCES_PROPERTY.equals(propName)) {
|
||||||
|
if (getShowMarkedOccurrences()) {
|
||||||
|
refreshMarkers();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If parser notices changed.
|
||||||
|
// TODO: Don't update "mark all/occurrences" markers.
|
||||||
|
else if (RSyntaxTextArea.PARSER_NOTICES_PROPERTY.equals(propName)) {
|
||||||
|
refreshMarkers();
|
||||||
|
}
|
||||||
|
|
||||||
|
// If marked occurrences changed.
|
||||||
|
// TODO: Only update "mark occurrences" markers, not all of them.
|
||||||
|
else if (RSyntaxTextArea.MARKED_OCCURRENCES_CHANGED_PROPERTY.
|
||||||
|
equals(propName)) {
|
||||||
|
if (getShowMarkedOccurrences()) {
|
||||||
|
refreshMarkers();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If "mark all" occurrences changed.
|
||||||
|
// TODO: Only update "mark all" markers, not all of them.
|
||||||
|
else if (RTextArea.MARK_ALL_OCCURRENCES_CHANGED_PROPERTY.
|
||||||
|
equals(propName)) {
|
||||||
|
if (getShowMarkAll()) {
|
||||||
|
refreshMarkers();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A notice that wraps a "marked occurrence" instance.
|
||||||
|
*/
|
||||||
|
private class MarkedOccurrenceNotice implements ParserNotice {
|
||||||
|
|
||||||
|
private DocumentRange range;
|
||||||
|
private Color color;
|
||||||
|
|
||||||
|
MarkedOccurrenceNotice(DocumentRange range, Color color) {
|
||||||
|
this.range = range;
|
||||||
|
this.color = color;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int compareTo(ParserNotice other) {
|
||||||
|
return 0; // Value doesn't matter
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean containsPosition(int pos) {
|
||||||
|
return pos>=range.getStartOffset() && pos<range.getEndOffset();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
// FindBugs - Define equals() when defining compareTo()
|
||||||
|
if (!(o instanceof ParserNotice)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return compareTo((ParserNotice)o)==0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Color getColor() {
|
||||||
|
return color;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean getKnowsOffsetAndLength() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getLength() {
|
||||||
|
return range.getEndOffset() - range.getStartOffset();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Level getLevel() {
|
||||||
|
return Level.INFO; // Won't matter
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getLine() {
|
||||||
|
try {
|
||||||
|
return textArea.getLineOfOffset(range.getStartOffset())+1;
|
||||||
|
} catch (BadLocationException ble) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getMessage() {
|
||||||
|
String text = null;
|
||||||
|
try {
|
||||||
|
String word = textArea.getText(range.getStartOffset(),
|
||||||
|
getLength());
|
||||||
|
text = MSG.getString("OccurrenceOf");
|
||||||
|
text = MessageFormat.format(text, word);
|
||||||
|
} catch (BadLocationException ble) {
|
||||||
|
UIManager.getLookAndFeel().provideErrorFeedback(textArea);
|
||||||
|
}
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getOffset() {
|
||||||
|
return range.getStartOffset();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Parser getParser() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean getShowInEditor() {
|
||||||
|
return false; // Value doesn't matter
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getToolTipText() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() { // FindBugs, since we override equals()
|
||||||
|
return 0; // Value doesn't matter for us.
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A "marker" in this error strip, representing one or more notices.
|
||||||
|
*/
|
||||||
|
private class Marker extends JComponent {
|
||||||
|
|
||||||
|
private List<ParserNotice> notices;
|
||||||
|
|
||||||
|
Marker(ParserNotice notice) {
|
||||||
|
notices = new ArrayList<ParserNotice>(1); // Usually just 1
|
||||||
|
addNotice(notice);
|
||||||
|
setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
|
||||||
|
setSize(getPreferredSize());
|
||||||
|
ToolTipManager.sharedInstance().registerComponent(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addNotice(ParserNotice notice) {
|
||||||
|
notices.add(notice);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean containsMarkedOccurence() {
|
||||||
|
boolean result = false;
|
||||||
|
for (int i=0; i<notices.size(); i++) {
|
||||||
|
if (notices.get(i) instanceof MarkedOccurrenceNotice) {
|
||||||
|
result = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Color getColor() {
|
||||||
|
// Return the color for the highest-level parser.
|
||||||
|
Color c = null;
|
||||||
|
int lowestLevel = Integer.MAX_VALUE; // ERROR is 0
|
||||||
|
for (ParserNotice notice : notices) {
|
||||||
|
if (notice.getLevel().getNumericValue()<lowestLevel) {
|
||||||
|
lowestLevel = notice.getLevel().getNumericValue();
|
||||||
|
c = notice.getColor();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Dimension getPreferredSize() {
|
||||||
|
int w = PREFERRED_WIDTH - 4; // 2-pixel empty border
|
||||||
|
return new Dimension(w, 5);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getToolTipText() {
|
||||||
|
return markerToolTipProvider.getToolTipText(Collections.
|
||||||
|
unmodifiableList(notices));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void mouseClicked(MouseEvent e) {
|
||||||
|
ParserNotice pn = notices.get(0);
|
||||||
|
int offs = pn.getOffset();
|
||||||
|
int len = pn.getLength();
|
||||||
|
if (offs>-1 && len>-1) { // These values are optional
|
||||||
|
DocumentRange range = new DocumentRange(offs, offs + len);
|
||||||
|
RSyntaxUtilities.selectAndPossiblyCenter(textArea, range, true);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
int line = pn.getLine();
|
||||||
|
try {
|
||||||
|
offs = textArea.getLineStartOffset(line);
|
||||||
|
textArea.getFoldManager().ensureOffsetNotInClosedFold(offs);
|
||||||
|
textArea.setCaretPosition(offs);
|
||||||
|
} catch (BadLocationException ble) { // Never happens
|
||||||
|
UIManager.getLookAndFeel().provideErrorFeedback(textArea);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void paintComponent(Graphics g) {
|
||||||
|
|
||||||
|
// TODO: Give "priorities" and always pick color of a notice with
|
||||||
|
// highest priority (e.g. parsing errors will usually be red).
|
||||||
|
|
||||||
|
Color borderColor = getColor();
|
||||||
|
if (borderColor==null) {
|
||||||
|
borderColor = Color.DARK_GRAY;
|
||||||
|
}
|
||||||
|
Color fillColor = getBrighterColor(borderColor);
|
||||||
|
|
||||||
|
int w = getWidth();
|
||||||
|
int h = getHeight();
|
||||||
|
|
||||||
|
g.setColor(fillColor);
|
||||||
|
g.fillRect(0,0, w,h);
|
||||||
|
|
||||||
|
g.setColor(borderColor);
|
||||||
|
g.drawRect(0,0, w-1,h-1);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removeNotify() {
|
||||||
|
super.removeNotify();
|
||||||
|
ToolTipManager.sharedInstance().unregisterComponent(this);
|
||||||
|
removeMouseListener(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void updateLocation() {
|
||||||
|
int line = notices.get(0).getLine();
|
||||||
|
int y = lineToY(line);
|
||||||
|
setLocation(2, y);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
Line=Line: {0}
|
||||||
|
MultipleMarkers=Multiple markers at this line:
|
||||||
|
OccurrenceOf=Occurrence of "{0}"
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
Line=Line: {0}
|
||||||
|
MultipleMarkers=\u0647\u0646\u0627\u0643 \u0639\u062f\u0629 \u062a\u062d\u062f\u064a\u062f\u0627\u062a \u0641\u064a \u0647\u0630\u0627 \u0627\u0644\u0633\u0637\u0631:
|
||||||
|
OccurrenceOf=\u0639\u062f\u062f \u0645\u0631\u0627\u062a \u062d\u062f\u0648\u062b"{0}"
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
Line=Zeile: {0}
|
||||||
|
MultipleMarkers=Mehrfache Markierungen in dieser Zeile:
|
||||||
|
OccurrenceOf=Auftreten von "{0}"
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
Line=Linea: {0}
|
||||||
|
MultipleMarkers=M\u00faltiples marcadores en esta linea:
|
||||||
|
OccurrenceOf=Ocurrencia de "{0}"
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
Line=Ligne: {0}
|
||||||
|
MultipleMarkers=Plusieurs rep\u00e8res sur cette ligne:
|
||||||
|
OccurrenceOf=Pr\u00e9sence de "{0}"
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
Line=Sor: {0}
|
||||||
|
MultipleMarkers=T\u00f6bb jel\u00f6l\u0151 ebben a sorban:
|
||||||
|
OccurrenceOf=A(z) "{0}" el\u0151fordul\u00e1sa
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
Line=Line: {0}
|
||||||
|
MultipleMarkers=Multiple markers at this line:
|
||||||
|
OccurrenceOf=Occurrence of "{0}"
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
Line=Linea: {0}
|
||||||
|
MultipleMarkers=Multipli punti a questa linea:
|
||||||
|
OccurrenceOf=Occorrenze di "{0}"
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
Line=\u884c: {0}
|
||||||
|
MultipleMarkers=\u3053\u306e\u884c\u306e\u4e2d\u306b\u3044\u304f\u3064\u306e\u30de\u30fc\u30ab\u30fc\u306f\u3042\u308a\u307e\u3059:
|
||||||
|
OccurrenceOf=\u51fa\u73fe\u56de\u6570 "{0}"
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
Line=\ud589: {0}
|
||||||
|
MultipleMarkers=\uc774 \ud589\uc5d0 \ubcf5\uc218\uc758 \ub9c8\ucee4 \uc874\uc7ac:
|
||||||
|
OccurrenceOf="{0}" \ubc1c\uc0dd
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
Line=Line: {0}
|
||||||
|
MultipleMarkers=Multiple markers at this line:
|
||||||
|
OccurrenceOf=Occurrence of "{0}"
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
Line=Linia: {0}
|
||||||
|
MultipleMarkers=Wiele znacznik w tej linii:
|
||||||
|
OccurrenceOf=Wyst\u0119powanie "{0}"
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
Line=Linha: {0}
|
||||||
|
MultipleMarkers=M\u00faltiplos marcadores nesta linha:
|
||||||
|
OccurrenceOf=Ocorr\u00eancia de: "{0}"
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
Line=\u0421\u0442\u0440\u043e\u043a\u0430: {0}
|
||||||
|
MultipleMarkers=\u041d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u043c\u0430\u0440\u043a\u0435\u0440\u043e\u0432 \u0432 \u044d\u0442\u043e\u0439 \u0441\u0442\u0440\u043e\u043a\u0435:
|
||||||
|
OccurrenceOf=\u0412\u043e\u0437\u043d\u0438\u043a\u043d\u043e\u0432\u0435\u043d\u0438\u0435 "{0}"
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
Line=Sat\u0131r: {0}
|
||||||
|
MultipleMarkers=Bu sat\u0131rda birden fazla i\u015faret\u00e7i:
|
||||||
|
OccurrenceOf="{0}"\u0131n bulunmas\u0131
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
Line=\u884c: {0}
|
||||||
|
MultipleMarkers=\u6b64\u884c\u6709\u591a\u6807\u8bb0
|
||||||
|
OccurrenceOf=\u6709"{0}"\u51fa\u73b0
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
Line=Line: {0}
|
||||||
|
MultipleMarkers=Multiple markers at this line:
|
||||||
|
OccurrenceOf=Occurrence of "{0}"
|
||||||
@@ -0,0 +1,121 @@
|
|||||||
|
/*
|
||||||
|
* 11/13/2008
|
||||||
|
*
|
||||||
|
* FileFileLocation.java - The location of a local file.
|
||||||
|
*
|
||||||
|
* This library is distributed under a modified BSD license. See the included
|
||||||
|
* RSyntaxTextArea.License.txt file for details.
|
||||||
|
*/
|
||||||
|
package org.fife.ui.rsyntaxtextarea;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The location of a local file.
|
||||||
|
*
|
||||||
|
* @author Robert Futrell
|
||||||
|
* @version 1.0
|
||||||
|
*/
|
||||||
|
class FileFileLocation extends FileLocation {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The file. This may or may not actually exist.
|
||||||
|
*/
|
||||||
|
private File file;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
*
|
||||||
|
* @param file The local file.
|
||||||
|
*/
|
||||||
|
FileFileLocation(File file) {
|
||||||
|
try {
|
||||||
|
// Useful on Windows and OS X.
|
||||||
|
this.file = file.getCanonicalFile();
|
||||||
|
} catch (IOException ioe) {
|
||||||
|
this.file = file;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected long getActualLastModified() {
|
||||||
|
return file.lastModified();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the full path to the file.
|
||||||
|
*
|
||||||
|
* @return The full path to the file.
|
||||||
|
* @see #getFileName()
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String getFileFullPath() {
|
||||||
|
return file.getAbsolutePath();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String getFileName() {
|
||||||
|
return file.getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected InputStream getInputStream() throws IOException {
|
||||||
|
return new FileInputStream(file);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected OutputStream getOutputStream() throws IOException {
|
||||||
|
return new FileOutputStream(file);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Always returns <code>true</code>.
|
||||||
|
*
|
||||||
|
* @return <code>true</code> always.
|
||||||
|
* @see #isLocalAndExists()
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean isLocal() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Since file locations of this type are guaranteed to be local, this
|
||||||
|
* method returns whether the file exists.
|
||||||
|
*
|
||||||
|
* @return Whether this local file actually exists.
|
||||||
|
* @see #isLocal()
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean isLocalAndExists() {
|
||||||
|
return file.exists();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,152 @@
|
|||||||
|
/*
|
||||||
|
* 11/13/2008
|
||||||
|
*
|
||||||
|
* FileLocation.java - Holds the location of a local or remote file.
|
||||||
|
*
|
||||||
|
* This library is distributed under a modified BSD license. See the included
|
||||||
|
* RSyntaxTextArea.License.txt file for details.
|
||||||
|
*/
|
||||||
|
package org.fife.ui.rsyntaxtextarea;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.net.MalformedURLException;
|
||||||
|
import java.net.URL;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Holds the location of a local or remote file. This provides a common way
|
||||||
|
* to read, write, and check properties of both local and remote files.
|
||||||
|
*
|
||||||
|
* @author Robert Futrell
|
||||||
|
* @version 1.0
|
||||||
|
*/
|
||||||
|
public abstract class FileLocation {
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a {@link FileLocation} instance for the specified local file.
|
||||||
|
*
|
||||||
|
* @param fileFullPath The full path to a local file.
|
||||||
|
* @return The file's location.
|
||||||
|
*/
|
||||||
|
public static FileLocation create(String fileFullPath) {
|
||||||
|
if (fileFullPath.startsWith("http://") ||
|
||||||
|
fileFullPath.startsWith("https://") ||
|
||||||
|
fileFullPath.startsWith("ftp://")) {
|
||||||
|
try {
|
||||||
|
return new URLFileLocation(new URL(fileFullPath));
|
||||||
|
} catch (MalformedURLException mue) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"Not a valid URL: " + fileFullPath, mue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new FileFileLocation(new File(fileFullPath));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a {@link FileLocation} instance for the specified local file.
|
||||||
|
*
|
||||||
|
* @param file A local file.
|
||||||
|
* @return The file's location.
|
||||||
|
*/
|
||||||
|
public static FileLocation create(File file) {
|
||||||
|
return new FileFileLocation(file);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a {@link FileLocation} instance for the specified file.
|
||||||
|
*
|
||||||
|
* @param url The URL of a file.
|
||||||
|
* @return The file's location.
|
||||||
|
*/
|
||||||
|
public static FileLocation create(URL url) {
|
||||||
|
if ("file".equalsIgnoreCase(url.getProtocol())) {
|
||||||
|
return new FileFileLocation(new File(url.getPath()));
|
||||||
|
}
|
||||||
|
return new URLFileLocation(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the last time this file was modified, or
|
||||||
|
* {@link TextEditorPane#LAST_MODIFIED_UNKNOWN} if this value cannot be
|
||||||
|
* computed (such as for a remote file).
|
||||||
|
*
|
||||||
|
* @return The last time this file was modified.
|
||||||
|
*/
|
||||||
|
protected abstract long getActualLastModified();
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the full path to the file. This will be stripped of
|
||||||
|
* sensitive information such as passwords for remote files.
|
||||||
|
*
|
||||||
|
* @return The full path to the file.
|
||||||
|
* @see #getFileName()
|
||||||
|
*/
|
||||||
|
public abstract String getFileFullPath();
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the name of the file.
|
||||||
|
*
|
||||||
|
* @return The name of the file.
|
||||||
|
* @see #getFileFullPath()
|
||||||
|
*/
|
||||||
|
public abstract String getFileName();
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Opens an input stream for reading from this file.
|
||||||
|
*
|
||||||
|
* @return The input stream.
|
||||||
|
* @throws IOException If the file does not exist, or some other IO error
|
||||||
|
* occurs.
|
||||||
|
*/
|
||||||
|
protected abstract InputStream getInputStream() throws IOException;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Opens an output stream for writing this file.
|
||||||
|
*
|
||||||
|
* @return An output stream.
|
||||||
|
* @throws IOException If an IO error occurs.
|
||||||
|
*/
|
||||||
|
protected abstract OutputStream getOutputStream() throws IOException;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether this file location is a local file.
|
||||||
|
*
|
||||||
|
* @return Whether this is a local file.
|
||||||
|
* @see #isLocalAndExists()
|
||||||
|
*/
|
||||||
|
public abstract boolean isLocal();
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether this file location is a local file that already
|
||||||
|
* exists.
|
||||||
|
*
|
||||||
|
* @return Whether this file is local and actually exists.
|
||||||
|
* @see #isLocal()
|
||||||
|
*/
|
||||||
|
public abstract boolean isLocalAndExists();
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether this file location is a remote location.
|
||||||
|
*
|
||||||
|
* @return Whether this is a remote file location.
|
||||||
|
*/
|
||||||
|
public boolean isRemote() {
|
||||||
|
return !isLocal();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,251 @@
|
|||||||
|
/*
|
||||||
|
* 03/07/2012
|
||||||
|
*
|
||||||
|
* FoldingAwareIconRowHeader - Icon row header that paints itself correctly
|
||||||
|
* even when code folding is enabled.
|
||||||
|
*
|
||||||
|
* This library is distributed under a modified BSD license. See the included
|
||||||
|
* RSyntaxTextArea.License.txt file for details.
|
||||||
|
*/
|
||||||
|
package org.fife.ui.rsyntaxtextarea;
|
||||||
|
|
||||||
|
import java.awt.Color;
|
||||||
|
import java.awt.Graphics;
|
||||||
|
import java.awt.Point;
|
||||||
|
import javax.swing.Icon;
|
||||||
|
import javax.swing.text.BadLocationException;
|
||||||
|
import javax.swing.text.Document;
|
||||||
|
import javax.swing.text.Element;
|
||||||
|
|
||||||
|
import org.fife.ui.rsyntaxtextarea.folding.FoldManager;
|
||||||
|
import org.fife.ui.rtextarea.GutterIconInfo;
|
||||||
|
import org.fife.ui.rtextarea.IconRowHeader;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A row header component that takes code folding into account when painting
|
||||||
|
* itself.
|
||||||
|
*
|
||||||
|
* @author Robert Futrell
|
||||||
|
* @version 1.0
|
||||||
|
*/
|
||||||
|
public class FoldingAwareIconRowHeader extends IconRowHeader {
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
*
|
||||||
|
* @param textArea The parent text area.
|
||||||
|
*/
|
||||||
|
public FoldingAwareIconRowHeader(RSyntaxTextArea textArea) {
|
||||||
|
super(textArea);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected void paintComponent(Graphics g) {
|
||||||
|
|
||||||
|
// When line wrap is not enabled, take the faster code path.
|
||||||
|
if (textArea==null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
RSyntaxTextArea rsta = (RSyntaxTextArea)textArea;
|
||||||
|
FoldManager fm = rsta.getFoldManager();
|
||||||
|
if (!fm.isCodeFoldingSupportedAndEnabled()) {
|
||||||
|
super.paintComponent(g);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
visibleRect = g.getClipBounds(visibleRect);
|
||||||
|
if (visibleRect==null) { // ???
|
||||||
|
visibleRect = getVisibleRect();
|
||||||
|
}
|
||||||
|
//System.out.println("IconRowHeader repainting: " + visibleRect);
|
||||||
|
if (visibleRect==null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
paintBackgroundImpl(g, visibleRect);
|
||||||
|
|
||||||
|
if (textArea.getLineWrap()) {
|
||||||
|
paintComponentWrapped(g);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Document doc = textArea.getDocument();
|
||||||
|
Element root = doc.getDefaultRootElement();
|
||||||
|
textAreaInsets = textArea.getInsets(textAreaInsets);
|
||||||
|
if (visibleRect.y<textAreaInsets.top) {
|
||||||
|
visibleRect.height -= (textAreaInsets.top - visibleRect.y);
|
||||||
|
visibleRect.y = textAreaInsets.top;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the first line to paint.
|
||||||
|
int cellHeight = textArea.getLineHeight();
|
||||||
|
int topLine = (visibleRect.y-textAreaInsets.top)/cellHeight;
|
||||||
|
|
||||||
|
// Get where to start painting (top of the row).
|
||||||
|
// We need to be "scrolled up" up just enough for the missing part of
|
||||||
|
// the first line.
|
||||||
|
int y = topLine*cellHeight + textAreaInsets.top;
|
||||||
|
|
||||||
|
// AFTER calculating visual offset to paint at, account for folding.
|
||||||
|
topLine += fm.getHiddenLineCountAbove(topLine, true);
|
||||||
|
|
||||||
|
// Paint the active line range.
|
||||||
|
if (activeLineRangeStart>-1 && activeLineRangeEnd>-1) {
|
||||||
|
Color activeLineRangeColor = getActiveLineRangeColor();
|
||||||
|
g.setColor(activeLineRangeColor);
|
||||||
|
try {
|
||||||
|
|
||||||
|
int realY1 = rsta.yForLine(activeLineRangeStart);
|
||||||
|
if (realY1>-1) { // Not in a collapsed fold...
|
||||||
|
|
||||||
|
int y1 = realY1;//Math.max(y, realY1);
|
||||||
|
|
||||||
|
int y2 = rsta.yForLine(activeLineRangeEnd);
|
||||||
|
if (y2==-1) { // In a collapsed fold
|
||||||
|
y2 = y1;
|
||||||
|
}
|
||||||
|
y2 += cellHeight - 1;
|
||||||
|
|
||||||
|
if (y2<visibleRect.y || y1>visibleRect.y+visibleRect.height) {
|
||||||
|
//System.out.println("... nothing to paint, bailing...");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
y1 = Math.max(y, realY1);
|
||||||
|
y2 = Math.min(y2, visibleRect.y+visibleRect.height);
|
||||||
|
//System.out.println(y1 + "... " + y2 + "; " + realY1 + ", " + visibleRect);
|
||||||
|
|
||||||
|
int j = y1;
|
||||||
|
while (j<=y2) {
|
||||||
|
int yEnd = Math.min(y2, j+getWidth());
|
||||||
|
int xEnd = yEnd-j;
|
||||||
|
g.drawLine(0,j, xEnd,yEnd);
|
||||||
|
j += 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
int i = 2;
|
||||||
|
while (i<getWidth()) {
|
||||||
|
int yEnd = y1 + getWidth() - i;
|
||||||
|
g.drawLine(i,y1, getWidth(),yEnd);
|
||||||
|
i += 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (realY1>=y && realY1<visibleRect.y+visibleRect.height) {
|
||||||
|
g.drawLine(0,realY1, getWidth(),realY1);
|
||||||
|
}
|
||||||
|
if (y2>=y && y2<visibleRect.y+visibleRect.height) {
|
||||||
|
g.drawLine(0,y2, getWidth(),y2);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (BadLocationException ble) {
|
||||||
|
ble.printStackTrace(); // Never happens
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Paint icons
|
||||||
|
if (trackingIcons!=null) {
|
||||||
|
int lastLine = textArea.getLineCount() - 1;
|
||||||
|
for (int i=trackingIcons.size()-1; i>=0; i--) { // Last to first
|
||||||
|
GutterIconInfo ti = getTrackingIcon(i);
|
||||||
|
int offs = ti.getMarkedOffset();
|
||||||
|
if (offs>=0 && offs<=doc.getLength()) {
|
||||||
|
int line = root.getElementIndex(offs);
|
||||||
|
if (line<=lastLine && line>=topLine) {
|
||||||
|
try {
|
||||||
|
Icon icon = ti.getIcon();
|
||||||
|
if (icon!=null) {
|
||||||
|
int lineY = rsta.yForLine(line);
|
||||||
|
if (lineY>=y && lineY<=visibleRect.y+visibleRect.height) {
|
||||||
|
int y2 = lineY + (cellHeight-icon.getIconHeight())/2;
|
||||||
|
icon.paintIcon(this, g, 0, y2);
|
||||||
|
lastLine = line-1; // Paint only 1 icon per line
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (BadLocationException ble) {
|
||||||
|
ble.printStackTrace(); // Never happens
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (line<topLine) {
|
||||||
|
break; // All other lines are above us, so quit now
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Paints icons when line wrapping is enabled. Note that this does not
|
||||||
|
* override the parent class's implementation to avoid this version being
|
||||||
|
* called when line wrapping is disabled.
|
||||||
|
*/
|
||||||
|
private void paintComponentWrapped(Graphics g) {
|
||||||
|
|
||||||
|
// The variables we use are as follows:
|
||||||
|
// - visibleRect is the "visible" area of the text area; e.g.
|
||||||
|
// [0,100, 300,100+(lineCount*cellHeight)-1].
|
||||||
|
// actualTop.y is the topmost-pixel in the first logical line we
|
||||||
|
// paint. Note that we may well not paint this part of the logical
|
||||||
|
// line, as it may be broken into many physical lines, with the first
|
||||||
|
// few physical lines scrolled past. Note also that this is NOT the
|
||||||
|
// visible rect of this line number list; this line number list has
|
||||||
|
// visible rect == [0,0, insets.left-1,visibleRect.height-1].
|
||||||
|
|
||||||
|
// We avoid using modelToView/viewToModel where possible, as these
|
||||||
|
// methods trigger a parsing of the line into syntax tokens, which is
|
||||||
|
// costly. It's cheaper to just grab the child views' bounds.
|
||||||
|
|
||||||
|
RSyntaxTextArea rsta = (RSyntaxTextArea)textArea;
|
||||||
|
// boolean currentLineHighlighted = textArea.getHighlightCurrentLine();
|
||||||
|
Document doc = textArea.getDocument();
|
||||||
|
Element root = doc.getDefaultRootElement();
|
||||||
|
int topPosition = textArea.viewToModel(
|
||||||
|
new Point(visibleRect.x,visibleRect.y));
|
||||||
|
int topLine = root.getElementIndex(topPosition);
|
||||||
|
|
||||||
|
int topY = visibleRect.y;
|
||||||
|
int bottomY = visibleRect.y + visibleRect.height;
|
||||||
|
int cellHeight = textArea.getLineHeight();
|
||||||
|
|
||||||
|
// Paint icons
|
||||||
|
if (trackingIcons!=null) {
|
||||||
|
int lastLine = textArea.getLineCount() - 1;
|
||||||
|
for (int i=trackingIcons.size()-1; i>=0; i--) { // Last to first
|
||||||
|
GutterIconInfo ti = getTrackingIcon(i);
|
||||||
|
Icon icon = ti.getIcon();
|
||||||
|
if (icon!=null) {
|
||||||
|
int iconH = icon.getIconHeight();
|
||||||
|
int offs = ti.getMarkedOffset();
|
||||||
|
if (offs>=0 && offs<=doc.getLength()) {
|
||||||
|
int line = root.getElementIndex(offs);
|
||||||
|
if (line<=lastLine && line>=topLine) {
|
||||||
|
try {
|
||||||
|
int lineY = rsta.yForLine(line);
|
||||||
|
if (lineY<=bottomY && (lineY+iconH>=topY)) {
|
||||||
|
int y2 = lineY + (cellHeight-iconH)/2;
|
||||||
|
ti.getIcon().paintIcon(this, g, 0, y2);
|
||||||
|
lastLine = line-1; // Paint only 1 icon per line
|
||||||
|
}
|
||||||
|
} catch (BadLocationException ble) {
|
||||||
|
ble.printStackTrace(); // Never happens
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (line<topLine) {
|
||||||
|
break; // All other lines are above us, so quit now
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,411 @@
|
|||||||
|
/*
|
||||||
|
* 12/01/2014
|
||||||
|
*
|
||||||
|
* Copyright (C) 2013 Robert Futrell
|
||||||
|
* robert_futrell at users.sourceforge.net
|
||||||
|
* http://fifesoft.com/rsyntaxtextarea
|
||||||
|
*
|
||||||
|
* This library is distributed under a modified BSD license. See the included
|
||||||
|
* RSyntaxTextArea.License.txt file for details.
|
||||||
|
*/
|
||||||
|
package org.fife.ui.rsyntaxtextarea;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import javax.swing.text.BadLocationException;
|
||||||
|
import javax.swing.text.Element;
|
||||||
|
|
||||||
|
import org.fife.ui.rtextarea.SmartHighlightPainter;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Marks occurrences of the current token for HTML. Tags that require a
|
||||||
|
* closing tag have their "opposite" tag closed.
|
||||||
|
*
|
||||||
|
* @author Robert Futrell
|
||||||
|
* @version 1.0
|
||||||
|
*/
|
||||||
|
public class HtmlOccurrenceMarker implements OccurrenceMarker {
|
||||||
|
|
||||||
|
private static final char[] CLOSE_TAG_START = { '<', '/' };
|
||||||
|
private static final char[] TAG_SELF_CLOSE = { '/', '>' };
|
||||||
|
|
||||||
|
private static final Set<String> TAGS_REQUIRING_CLOSING =
|
||||||
|
getRequiredClosingTags();
|
||||||
|
|
||||||
|
public static final Set<String> getRequiredClosingTags() {
|
||||||
|
final String[] tags = {
|
||||||
|
"html",
|
||||||
|
"head",
|
||||||
|
"title",
|
||||||
|
"style",
|
||||||
|
"script",
|
||||||
|
"noscript",
|
||||||
|
"body",
|
||||||
|
"section",
|
||||||
|
"nav",
|
||||||
|
"article",
|
||||||
|
"aside",
|
||||||
|
"h1",
|
||||||
|
"h2",
|
||||||
|
"h3",
|
||||||
|
"h4",
|
||||||
|
"h5",
|
||||||
|
"h6",
|
||||||
|
"header",
|
||||||
|
"footer",
|
||||||
|
"address",
|
||||||
|
"pre",
|
||||||
|
"dialog",
|
||||||
|
"blockquote",
|
||||||
|
"ol",
|
||||||
|
"ul",
|
||||||
|
"dl",
|
||||||
|
"a",
|
||||||
|
"q",
|
||||||
|
"cite",
|
||||||
|
"em",
|
||||||
|
"strong",
|
||||||
|
"small",
|
||||||
|
"mark",
|
||||||
|
"dfn",
|
||||||
|
"abbr",
|
||||||
|
"time",
|
||||||
|
"progress",
|
||||||
|
"meter",
|
||||||
|
"code",
|
||||||
|
"var",
|
||||||
|
"samp",
|
||||||
|
"kbd",
|
||||||
|
"sub",
|
||||||
|
"sup",
|
||||||
|
"span",
|
||||||
|
"i",
|
||||||
|
"b",
|
||||||
|
"bdo",
|
||||||
|
"ruby",
|
||||||
|
"rt",
|
||||||
|
"rp",
|
||||||
|
"ins",
|
||||||
|
"del",
|
||||||
|
"figure",
|
||||||
|
"iframe",
|
||||||
|
"object",
|
||||||
|
"video",
|
||||||
|
"audio",
|
||||||
|
"canvas",
|
||||||
|
"map",
|
||||||
|
"table",
|
||||||
|
"caption",
|
||||||
|
"form",
|
||||||
|
"fieldset",
|
||||||
|
"label",
|
||||||
|
"button",
|
||||||
|
"select",
|
||||||
|
"datalist",
|
||||||
|
"textarea",
|
||||||
|
"output",
|
||||||
|
"details",
|
||||||
|
"bb",
|
||||||
|
"menu",
|
||||||
|
"legend",
|
||||||
|
"div",
|
||||||
|
// Obsolete elements
|
||||||
|
"acronym",
|
||||||
|
"applet",
|
||||||
|
"big",
|
||||||
|
"blink",
|
||||||
|
"center",
|
||||||
|
"dir",
|
||||||
|
"font",
|
||||||
|
"frame",
|
||||||
|
"frameset",
|
||||||
|
"isindex",
|
||||||
|
"listing",
|
||||||
|
"marquee",
|
||||||
|
"nobr",
|
||||||
|
"noembed",
|
||||||
|
"noframes",
|
||||||
|
"plaintext",
|
||||||
|
"s",
|
||||||
|
"spacer",
|
||||||
|
"strike",
|
||||||
|
"tt",
|
||||||
|
"u",
|
||||||
|
"xmp",
|
||||||
|
};
|
||||||
|
return new HashSet<String>(Arrays.asList(tags));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If the caret is inside of a tag, this method returns the token
|
||||||
|
* representing the tag name; otherwise, <code>null</code> is returned.<p>
|
||||||
|
*
|
||||||
|
* Currently, this method only checks for tag names on the same line as
|
||||||
|
* the caret, for simplicity. In the future it could check prior lines
|
||||||
|
* until the tag name is found.
|
||||||
|
*
|
||||||
|
* @param textArea The text area.
|
||||||
|
* @param occurrenceMarker The occurrence marker.
|
||||||
|
* @return The token to mark occurrences of. Note that, if the
|
||||||
|
* specified occurrence marker identifies tokens other than
|
||||||
|
* tag names, these other element types may be returned.
|
||||||
|
*/
|
||||||
|
public static final Token getTagNameTokenForCaretOffset(
|
||||||
|
RSyntaxTextArea textArea, OccurrenceMarker occurrenceMarker) {
|
||||||
|
|
||||||
|
// Get the tag name token.
|
||||||
|
// For now, we only check for tags on the current line, for simplicity.
|
||||||
|
|
||||||
|
int dot = textArea.getCaretPosition();
|
||||||
|
Token t = textArea.getTokenListForLine(textArea.getCaretLineNumber());
|
||||||
|
Token toMark = null;
|
||||||
|
|
||||||
|
while (t!=null && t.isPaintable()) {
|
||||||
|
if (t.getType()==Token.MARKUP_TAG_NAME) {
|
||||||
|
toMark = t;
|
||||||
|
}
|
||||||
|
// Check for the token containing the caret before checking
|
||||||
|
// if it's the close token.
|
||||||
|
if (t.getEndOffset()==dot || t.containsPosition(dot)) {
|
||||||
|
// Some languages, like PHP, mark functions/variables (PHP,
|
||||||
|
// JavaScirpt) as well as HTML tags.
|
||||||
|
if (occurrenceMarker.isValidType(textArea, t) &&
|
||||||
|
t.getType()!=Token.MARKUP_TAG_NAME) {
|
||||||
|
return t;
|
||||||
|
}
|
||||||
|
if (t.containsPosition(dot)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (t.getType()==Token.MARKUP_TAG_DELIMITER) {
|
||||||
|
if (t.isSingleChar('>') || t.is(TAG_SELF_CLOSE)) {
|
||||||
|
toMark = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
t = t.getNextToken();
|
||||||
|
}
|
||||||
|
|
||||||
|
return toMark;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Token getTokenToMark(RSyntaxTextArea textArea) {
|
||||||
|
return getTagNameTokenForCaretOffset(textArea, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean isValidType(RSyntaxTextArea textArea, Token t) {
|
||||||
|
return textArea.getMarkOccurrencesOfTokenType(t.getType());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void markOccurrences(RSyntaxDocument doc, Token t,
|
||||||
|
RSyntaxTextAreaHighlighter h, SmartHighlightPainter p) {
|
||||||
|
|
||||||
|
if (t.getType()!=Token.MARKUP_TAG_NAME) {
|
||||||
|
DefaultOccurrenceMarker.markOccurrencesOfToken(doc, t, h, p);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String lexemeStr = t.getLexeme();
|
||||||
|
char[] lexeme = lexemeStr.toCharArray();
|
||||||
|
lexemeStr = lexemeStr.toLowerCase();
|
||||||
|
int tokenOffs = t.getOffset();
|
||||||
|
Element root = doc.getDefaultRootElement();
|
||||||
|
int lineCount = root.getElementCount();
|
||||||
|
int curLine = root.getElementIndex(t.getOffset());
|
||||||
|
int depth = 0;
|
||||||
|
|
||||||
|
// For now, we only check for tags on the current line, for
|
||||||
|
// simplicity. Tags spanning multiple lines aren't common anyway.
|
||||||
|
boolean found = false;
|
||||||
|
boolean forward = true;
|
||||||
|
t = doc.getTokenListForLine(curLine);
|
||||||
|
while (t!=null && t.isPaintable()) {
|
||||||
|
if (t.getType()==Token.MARKUP_TAG_DELIMITER) {
|
||||||
|
if (t.isSingleChar('<') && t.getOffset()+1==tokenOffs) {
|
||||||
|
// Don't try to match a tag that is optionally closed (or
|
||||||
|
// closing is forbidden entirely).
|
||||||
|
if (TAGS_REQUIRING_CLOSING.contains(lexemeStr)) {
|
||||||
|
found = true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else if (t.is(CLOSE_TAG_START) && t.getOffset()+2==tokenOffs) {
|
||||||
|
// Searching backward, we assume we can find the opening
|
||||||
|
// tag. Don't really care if it's valid or not.
|
||||||
|
found = true;
|
||||||
|
forward = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
t = t.getNextToken();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!found) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (forward) {
|
||||||
|
|
||||||
|
t = t.getNextToken().getNextToken();
|
||||||
|
|
||||||
|
do {
|
||||||
|
|
||||||
|
while (t!=null && t.isPaintable()) {
|
||||||
|
if (t.getType()==Token.MARKUP_TAG_DELIMITER) {
|
||||||
|
if (t.is(CLOSE_TAG_START)) {
|
||||||
|
Token match = t.getNextToken();
|
||||||
|
if (match!=null && match.is(lexeme)) {
|
||||||
|
if (depth>0) {
|
||||||
|
depth--;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
try {
|
||||||
|
int end = match.getOffset() + match.length();
|
||||||
|
h.addMarkedOccurrenceHighlight(match.getOffset(), end, p);
|
||||||
|
end = tokenOffs + match.length();
|
||||||
|
h.addMarkedOccurrenceHighlight(tokenOffs, end, p);
|
||||||
|
} catch (BadLocationException ble) {
|
||||||
|
ble.printStackTrace(); // Never happens
|
||||||
|
}
|
||||||
|
return; // We're done!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (t.isSingleChar('<')) {
|
||||||
|
t = t.getNextToken();
|
||||||
|
if (t!=null && t.is(lexeme)) {
|
||||||
|
depth++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
t = t==null ? null : t.getNextToken();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (++curLine<lineCount) {
|
||||||
|
t = doc.getTokenListForLine(curLine);
|
||||||
|
}
|
||||||
|
|
||||||
|
} while (curLine<lineCount);
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
else { // !forward
|
||||||
|
|
||||||
|
// Idea: Get all opening and closing tags of the relevant type on
|
||||||
|
// the current line. Find the opening tag paired to the closing
|
||||||
|
// tag we found originally; if it's not on this line, keep going
|
||||||
|
// to the previous line.
|
||||||
|
|
||||||
|
List<Entry> openCloses = new ArrayList<Entry>();
|
||||||
|
boolean inPossibleMatch = false;
|
||||||
|
t = doc.getTokenListForLine(curLine);
|
||||||
|
final int endBefore = tokenOffs - 2; // Stop before "</".
|
||||||
|
|
||||||
|
do {
|
||||||
|
|
||||||
|
while (t!=null && t.getOffset()<endBefore && t.isPaintable()) {
|
||||||
|
if (t.getType()==Token.MARKUP_TAG_DELIMITER) {
|
||||||
|
if (t.isSingleChar('<')) {
|
||||||
|
Token next = t.getNextToken();
|
||||||
|
if (next!=null) {
|
||||||
|
if (next.is(lexeme)) {
|
||||||
|
openCloses.add(new Entry(true, next));
|
||||||
|
inPossibleMatch = true;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
inPossibleMatch = false;
|
||||||
|
}
|
||||||
|
t = next;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (t.isSingleChar('>')) {
|
||||||
|
inPossibleMatch = false;
|
||||||
|
}
|
||||||
|
else if (inPossibleMatch && t.is(TAG_SELF_CLOSE)) {
|
||||||
|
openCloses.remove(openCloses.size()-1);
|
||||||
|
inPossibleMatch = false;
|
||||||
|
}
|
||||||
|
else if (t.is(CLOSE_TAG_START)) {
|
||||||
|
Token next = t.getNextToken();
|
||||||
|
if (next!=null) {
|
||||||
|
// Invalid XML might not have a match
|
||||||
|
if (next.is(lexeme)) {
|
||||||
|
openCloses.add(new Entry(false, next));
|
||||||
|
}
|
||||||
|
t = next;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
t = t.getNextToken();
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i=openCloses.size()-1; i>=0; i--) {
|
||||||
|
Entry entry = openCloses.get(i);
|
||||||
|
depth += entry.open ? -1 : 1;
|
||||||
|
if (depth==-1) {
|
||||||
|
try {
|
||||||
|
Token match = entry.t;
|
||||||
|
int end = match.getOffset() + match.length();
|
||||||
|
h.addMarkedOccurrenceHighlight(match.getOffset(), end, p);
|
||||||
|
end = tokenOffs + match.length();
|
||||||
|
h.addMarkedOccurrenceHighlight(tokenOffs, end, p);
|
||||||
|
} catch (BadLocationException ble) {
|
||||||
|
ble.printStackTrace(); // Never happens
|
||||||
|
}
|
||||||
|
openCloses.clear();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
openCloses.clear();
|
||||||
|
if (--curLine>=0) {
|
||||||
|
t = doc.getTokenListForLine(curLine);
|
||||||
|
}
|
||||||
|
|
||||||
|
} while (curLine>=0);
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used internally when searching backward for a matching "open" tag.
|
||||||
|
*/
|
||||||
|
private static class Entry {
|
||||||
|
|
||||||
|
private boolean open;
|
||||||
|
private Token t;
|
||||||
|
|
||||||
|
Entry(boolean open, Token t) {
|
||||||
|
this.open = open;
|
||||||
|
this.t = t;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
/*
|
||||||
|
* 02/16/2012
|
||||||
|
*
|
||||||
|
* Copyright (C) 2013 Robert Futrell
|
||||||
|
* robert_futrell at users.sourceforge.net
|
||||||
|
* http://fifesoft.com/rsyntaxtextarea
|
||||||
|
*
|
||||||
|
* This library is distributed under a modified BSD license. See the included
|
||||||
|
* RSyntaxTextArea.License.txt file for details.
|
||||||
|
*/
|
||||||
|
package org.fife.ui.rsyntaxtextarea;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates hyperlinks in a document. If one of these is installed on an
|
||||||
|
* <code>RSyntaxTextArea</code> it is queried when the mouse is moved and
|
||||||
|
* hyperlinks are enabled. If the user is not hovering over a "real" hyperlink
|
||||||
|
* (e.g. "http://www.google.com"), the link generator is asked if a text region
|
||||||
|
* at the mouse position should be considered a hyperlink. If so, a result
|
||||||
|
* object is returned, describing exactly what region of text is the link, and
|
||||||
|
* where it goes to.<p>
|
||||||
|
*
|
||||||
|
* This interface is typically used by applications providing advanced support
|
||||||
|
* for programming languages, such as IDEs. For example, an implementation of
|
||||||
|
* this class could identify the token under the mouse position as a "variable,"
|
||||||
|
* and the hyperlink returned would select the variable's declaration in the
|
||||||
|
* document.
|
||||||
|
*
|
||||||
|
* @author Robert Futrell
|
||||||
|
* @version 1.0
|
||||||
|
*/
|
||||||
|
public interface LinkGenerator {
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If a region of text under the mouse position should be considered a
|
||||||
|
* hyperlink, a result object is returned. This object describes what
|
||||||
|
* region of text is the link, and what action to perform if the link is
|
||||||
|
* clicked.
|
||||||
|
*
|
||||||
|
* @param textArea The text component.
|
||||||
|
* @param offs The offset in the document under the mouse position.
|
||||||
|
* @return The link information, or <code>null</code> if no link is at the
|
||||||
|
* specified offset.
|
||||||
|
*/
|
||||||
|
LinkGeneratorResult isLinkAtOffset(RSyntaxTextArea textArea, int offs);
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
/*
|
||||||
|
* 02/16/2012
|
||||||
|
*
|
||||||
|
* Copyright (C) 2013 Robert Futrell
|
||||||
|
* robert_futrell at users.sourceforge.net
|
||||||
|
* http://fifesoft.com/rsyntaxtextarea
|
||||||
|
*
|
||||||
|
* This library is distributed under a modified BSD license. See the included
|
||||||
|
* RSyntaxTextArea.License.txt file for details.
|
||||||
|
*/
|
||||||
|
package org.fife.ui.rsyntaxtextarea;
|
||||||
|
|
||||||
|
import javax.swing.event.HyperlinkEvent;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A result object from a {@link LinkGenerator}. Implementations of this class
|
||||||
|
* specify what action to execute when the user clicks on the "link" specified
|
||||||
|
* by the <code>LinkGenerator</code>. Typically, this will do something like
|
||||||
|
* select another region of text in the document (the declaration of the
|
||||||
|
* variable at the mouse position), or open another file in the parent
|
||||||
|
* application, etc.
|
||||||
|
*
|
||||||
|
* @author Robert Futrell
|
||||||
|
* @version 1.0
|
||||||
|
* @see SelectRegionLinkGeneratorResult
|
||||||
|
*/
|
||||||
|
public interface LinkGeneratorResult {
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Executes the action associated with this object. If the result is a
|
||||||
|
* URL to open, a standard hyperlink event can be returned. Alternatively,
|
||||||
|
* <code>null</code> can be returned and the action performed in this
|
||||||
|
* method itself.
|
||||||
|
*
|
||||||
|
* @return The hyperlink event to broadcast from the text area, or
|
||||||
|
* <code>null</code> if the action's behavior occurs in this method
|
||||||
|
* directly.
|
||||||
|
*/
|
||||||
|
HyperlinkEvent execute();
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the starting offset of the link specified by the parent
|
||||||
|
* <code>LinkGenerator</code>.
|
||||||
|
*
|
||||||
|
* @return The offset.
|
||||||
|
*/
|
||||||
|
int getSourceOffset();
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,289 @@
|
|||||||
|
/*
|
||||||
|
* 01/06/2009
|
||||||
|
*
|
||||||
|
* MarkOccurrencesSupport.java - Handles marking all occurrences of the
|
||||||
|
* currently selected identifier in a text area.
|
||||||
|
*
|
||||||
|
* This library is distributed under a modified BSD license. See the included
|
||||||
|
* RSyntaxTextArea.License.txt file for details.
|
||||||
|
*/
|
||||||
|
package org.fife.ui.rsyntaxtextarea;
|
||||||
|
|
||||||
|
import java.awt.Color;
|
||||||
|
import java.awt.event.ActionEvent;
|
||||||
|
import java.awt.event.ActionListener;
|
||||||
|
|
||||||
|
import javax.swing.Timer;
|
||||||
|
import javax.swing.event.CaretEvent;
|
||||||
|
import javax.swing.event.CaretListener;
|
||||||
|
import javax.swing.text.Caret;
|
||||||
|
|
||||||
|
import org.fife.ui.rtextarea.SmartHighlightPainter;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Marks all occurrences of the token at the current caret position, if it is
|
||||||
|
* an identifier.
|
||||||
|
*
|
||||||
|
* @author Robert Futrell
|
||||||
|
* @version 1.0
|
||||||
|
* @see OccurrenceMarker
|
||||||
|
*/
|
||||||
|
class MarkOccurrencesSupport implements CaretListener, ActionListener {
|
||||||
|
|
||||||
|
private RSyntaxTextArea textArea;
|
||||||
|
private Timer timer;
|
||||||
|
private SmartHighlightPainter p;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The default color used to mark occurrences.
|
||||||
|
*/
|
||||||
|
static final Color DEFAULT_COLOR = new Color(224, 224, 224);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The default delay.
|
||||||
|
*/
|
||||||
|
static final int DEFAULT_DELAY_MS = 1000;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor. Creates a listener with a 1 second delay.
|
||||||
|
*/
|
||||||
|
MarkOccurrencesSupport() {
|
||||||
|
this(DEFAULT_DELAY_MS);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
*
|
||||||
|
* @param delay The delay between when the caret last moves and when the
|
||||||
|
* text should be scanned for matching occurrences. This should
|
||||||
|
* be in milliseconds.
|
||||||
|
*/
|
||||||
|
MarkOccurrencesSupport(int delay) {
|
||||||
|
this(delay, DEFAULT_COLOR);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
*
|
||||||
|
* @param delay The delay between when the caret last moves and when the
|
||||||
|
* text should be scanned for matching occurrences. This should
|
||||||
|
* be in milliseconds.
|
||||||
|
* @param color The color to use to mark the occurrences. This cannot be
|
||||||
|
* <code>null</code>.
|
||||||
|
*/
|
||||||
|
MarkOccurrencesSupport(int delay, Color color) {
|
||||||
|
timer = new Timer(delay, this);
|
||||||
|
timer.setRepeats(false);
|
||||||
|
p = new SmartHighlightPainter();
|
||||||
|
setColor(color);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called after the caret has been moved and a fixed time delay has
|
||||||
|
* elapsed. This locates and highlights all occurrences of the identifier
|
||||||
|
* at the caret position, if any.<p>
|
||||||
|
*
|
||||||
|
* Callers should not call this method directly, but should rather prefer
|
||||||
|
* {@link #doMarkOccurrences()} to mark occurrences.
|
||||||
|
*
|
||||||
|
* @param e The event.
|
||||||
|
* @see #doMarkOccurrences()
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void actionPerformed(ActionEvent e) {
|
||||||
|
|
||||||
|
// Don't do anything if they are selecting text.
|
||||||
|
Caret c = textArea.getCaret();
|
||||||
|
if (c.getDot()!=c.getMark()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
RSyntaxDocument doc = (RSyntaxDocument)textArea.getDocument();
|
||||||
|
OccurrenceMarker occurrenceMarker = doc.getOccurrenceMarker();
|
||||||
|
boolean occurrencesChanged = false;
|
||||||
|
|
||||||
|
if (occurrenceMarker!=null) {
|
||||||
|
|
||||||
|
doc.readLock();
|
||||||
|
try {
|
||||||
|
|
||||||
|
Token t = occurrenceMarker.getTokenToMark(textArea);
|
||||||
|
|
||||||
|
if (t!=null && occurrenceMarker.isValidType(textArea, t) &&
|
||||||
|
!RSyntaxUtilities.isNonWordChar(t)) {
|
||||||
|
clear();
|
||||||
|
RSyntaxTextAreaHighlighter h = (RSyntaxTextAreaHighlighter)
|
||||||
|
textArea.getHighlighter();
|
||||||
|
occurrenceMarker.markOccurrences(doc, t, h, p);
|
||||||
|
//textArea.repaint();
|
||||||
|
// TODO: Do a textArea.repaint() instead of repainting each
|
||||||
|
// marker as it's added if count is huge
|
||||||
|
occurrencesChanged = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
} finally {
|
||||||
|
doc.readUnlock();
|
||||||
|
//time = System.currentTimeMillis() - time;
|
||||||
|
//System.out.println("MarkOccurrencesSupport took: " + time + " ms");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if (occurrencesChanged) {
|
||||||
|
textArea.fireMarkedOccurrencesChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the caret moves in the text area.
|
||||||
|
*
|
||||||
|
* @param e The event.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void caretUpdate(CaretEvent e) {
|
||||||
|
timer.restart();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes all highlights added to the text area by this listener.
|
||||||
|
*/
|
||||||
|
void clear() {
|
||||||
|
if (textArea!=null) {
|
||||||
|
RSyntaxTextAreaHighlighter h = (RSyntaxTextAreaHighlighter)
|
||||||
|
textArea.getHighlighter();
|
||||||
|
h.clearMarkOccurrencesHighlights();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Immediately marks all occurrences of the token at the current caret
|
||||||
|
* position.
|
||||||
|
*/
|
||||||
|
public void doMarkOccurrences() {
|
||||||
|
timer.stop();
|
||||||
|
actionPerformed(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the color being used to mark occurrences.
|
||||||
|
*
|
||||||
|
* @return The color being used.
|
||||||
|
* @see #setColor(Color)
|
||||||
|
*/
|
||||||
|
public Color getColor() {
|
||||||
|
return (Color)p.getPaint();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the delay, in milliseconds.
|
||||||
|
*
|
||||||
|
* @return The delay.
|
||||||
|
* @see #setDelay(int)
|
||||||
|
*/
|
||||||
|
public int getDelay() {
|
||||||
|
return timer.getDelay();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether a border is painted around marked occurrences.
|
||||||
|
*
|
||||||
|
* @return Whether a border is painted.
|
||||||
|
* @see #setPaintBorder(boolean)
|
||||||
|
* @see #getColor()
|
||||||
|
*/
|
||||||
|
public boolean getPaintBorder() {
|
||||||
|
return p.getPaintBorder();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Installs this listener on a text area. If it is already installed on
|
||||||
|
* another text area, it is uninstalled first.
|
||||||
|
*
|
||||||
|
* @param textArea The text area to install on.
|
||||||
|
*/
|
||||||
|
public void install(RSyntaxTextArea textArea) {
|
||||||
|
if (this.textArea!=null) {
|
||||||
|
uninstall();
|
||||||
|
}
|
||||||
|
this.textArea = textArea;
|
||||||
|
textArea.addCaretListener(this);
|
||||||
|
if (textArea.getMarkOccurrencesColor()!=null) {
|
||||||
|
setColor(textArea.getMarkOccurrencesColor());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the color to use when marking occurrences.
|
||||||
|
*
|
||||||
|
* @param color The color to use.
|
||||||
|
* @see #getColor()
|
||||||
|
* @see #setPaintBorder(boolean)
|
||||||
|
*/
|
||||||
|
public void setColor(Color color) {
|
||||||
|
p.setPaint(color);
|
||||||
|
if (textArea!=null) {
|
||||||
|
clear();
|
||||||
|
caretUpdate(null); // Force a highlight repaint.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the delay between the last caret position change and when the
|
||||||
|
* text is scanned for matching identifiers. A delay is needed to prevent
|
||||||
|
* repeated scanning while the user is typing.
|
||||||
|
*
|
||||||
|
* @param delay The new delay.
|
||||||
|
* @see #getDelay()
|
||||||
|
*/
|
||||||
|
public void setDelay(int delay) {
|
||||||
|
timer.setInitialDelay(delay);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Toggles whether a border is painted around marked highlights.
|
||||||
|
*
|
||||||
|
* @param paint Whether to paint a border.
|
||||||
|
* @see #getPaintBorder()
|
||||||
|
* @see #setColor(Color)
|
||||||
|
*/
|
||||||
|
public void setPaintBorder(boolean paint) {
|
||||||
|
if (paint!=p.getPaintBorder()) {
|
||||||
|
p.setPaintBorder(paint);
|
||||||
|
if (textArea!=null) {
|
||||||
|
textArea.repaint();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Uninstalls this listener from the current text area. Does nothing if
|
||||||
|
* it not currently installed on any text area.
|
||||||
|
*
|
||||||
|
* @see #install(RSyntaxTextArea)
|
||||||
|
*/
|
||||||
|
public void uninstall() {
|
||||||
|
if (textArea!=null) {
|
||||||
|
clear();
|
||||||
|
textArea.removeCaretListener(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,245 @@
|
|||||||
|
/*
|
||||||
|
* 07/03/2016
|
||||||
|
*
|
||||||
|
* This library is distributed under a modified BSD license. See the included
|
||||||
|
* RSyntaxTextArea.License.txt file for details.
|
||||||
|
*/
|
||||||
|
package org.fife.ui.rsyntaxtextarea;
|
||||||
|
|
||||||
|
import java.awt.BorderLayout;
|
||||||
|
import java.awt.Dimension;
|
||||||
|
import java.awt.Font;
|
||||||
|
import java.awt.Point;
|
||||||
|
import java.awt.Window;
|
||||||
|
import java.awt.event.ActionEvent;
|
||||||
|
import java.awt.event.ComponentEvent;
|
||||||
|
import java.awt.event.ComponentListener;
|
||||||
|
import java.awt.event.KeyEvent;
|
||||||
|
import java.awt.event.WindowAdapter;
|
||||||
|
import java.awt.event.WindowEvent;
|
||||||
|
|
||||||
|
import javax.swing.AbstractAction;
|
||||||
|
import javax.swing.ActionMap;
|
||||||
|
import javax.swing.BorderFactory;
|
||||||
|
import javax.swing.InputMap;
|
||||||
|
import javax.swing.JComponent;
|
||||||
|
import javax.swing.JLabel;
|
||||||
|
import javax.swing.JPanel;
|
||||||
|
import javax.swing.JWindow;
|
||||||
|
import javax.swing.KeyStroke;
|
||||||
|
import javax.swing.SwingUtilities;
|
||||||
|
import javax.swing.UIManager;
|
||||||
|
import javax.swing.text.BadLocationException;
|
||||||
|
|
||||||
|
import org.fife.ui.rsyntaxtextarea.focusabletip.TipUtil;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A tool tip-like popup that shows the line of code containing the bracket
|
||||||
|
* matched to that at the caret position, if it is scrolled out of the user's
|
||||||
|
* viewport.
|
||||||
|
*
|
||||||
|
* @author Robert Futrell
|
||||||
|
* @version 1.0
|
||||||
|
*/
|
||||||
|
class MatchedBracketPopup extends JWindow {
|
||||||
|
|
||||||
|
private RSyntaxTextArea textArea;
|
||||||
|
|
||||||
|
private transient Listener listener;
|
||||||
|
|
||||||
|
private static final int LEFT_EMPTY_BORDER = 5;
|
||||||
|
|
||||||
|
|
||||||
|
MatchedBracketPopup(Window parent, RSyntaxTextArea textArea, int
|
||||||
|
offsToRender) {
|
||||||
|
|
||||||
|
super(parent);
|
||||||
|
this.textArea = textArea;
|
||||||
|
JPanel cp = new JPanel(new BorderLayout());
|
||||||
|
cp.setBorder(BorderFactory.createCompoundBorder(
|
||||||
|
TipUtil.getToolTipBorder(),
|
||||||
|
BorderFactory.createEmptyBorder(2, LEFT_EMPTY_BORDER, 5, 5)));
|
||||||
|
cp.setBackground(TipUtil.getToolTipBackground());
|
||||||
|
setContentPane(cp);
|
||||||
|
|
||||||
|
cp.add(new JLabel(getText(offsToRender)));
|
||||||
|
|
||||||
|
installKeyBindings();
|
||||||
|
listener = new Listener();
|
||||||
|
setLocation();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Overridden to ensure this popup stays in a specific size range.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Dimension getPreferredSize() {
|
||||||
|
Dimension size = super.getPreferredSize();
|
||||||
|
if (size!=null) {
|
||||||
|
size.width = Math.min(size.width, 800);
|
||||||
|
}
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private String getText(int offsToRender) {
|
||||||
|
|
||||||
|
int line = 0;
|
||||||
|
try {
|
||||||
|
line = textArea.getLineOfOffset(offsToRender);
|
||||||
|
} catch (BadLocationException ble) {
|
||||||
|
ble.printStackTrace(); // Never happens
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
int lastLine = line + 1;
|
||||||
|
|
||||||
|
// Render prior line if the open brace line has no other text on it
|
||||||
|
if (line > 0) {
|
||||||
|
try {
|
||||||
|
int startOffs = textArea.getLineStartOffset(line);
|
||||||
|
int length = textArea.getLineEndOffset(line) - startOffs;
|
||||||
|
String text = textArea.getText(startOffs, length);
|
||||||
|
if (text.trim().length() == 1) {
|
||||||
|
line--;
|
||||||
|
}
|
||||||
|
} catch (BadLocationException ble) {
|
||||||
|
UIManager.getLookAndFeel().provideErrorFeedback(textArea);
|
||||||
|
ble.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Font font = textArea.getFontForTokenType(TokenTypes.IDENTIFIER);
|
||||||
|
StringBuilder sb = new StringBuilder("<html>");
|
||||||
|
sb.append("<style>body { font-size:\"").append(font.getSize());
|
||||||
|
sb.append("pt\" }</style><nobr>");
|
||||||
|
while (line < lastLine) {
|
||||||
|
Token t = textArea.getTokenListForLine(line);
|
||||||
|
while (t!=null && t.isPaintable()) {
|
||||||
|
t.appendHTMLRepresentation(sb, textArea, true, true);
|
||||||
|
t = t.getNextToken();
|
||||||
|
}
|
||||||
|
sb.append("<br>");
|
||||||
|
line++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return sb.toString();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds key bindings to this popup.
|
||||||
|
*/
|
||||||
|
private void installKeyBindings() {
|
||||||
|
|
||||||
|
InputMap im = getRootPane().getInputMap(
|
||||||
|
JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
|
||||||
|
ActionMap am = getRootPane().getActionMap();
|
||||||
|
|
||||||
|
KeyStroke escapeKS = KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0);
|
||||||
|
im.put(escapeKS, "onEscape");
|
||||||
|
am.put("onEscape", new EscapeAction());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Positions this popup to be in the top right-hand corner of the parent
|
||||||
|
* editor.
|
||||||
|
*/
|
||||||
|
private void setLocation() {
|
||||||
|
Point topLeft = textArea.getVisibleRect().getLocation();
|
||||||
|
SwingUtilities.convertPointToScreen(topLeft, textArea);
|
||||||
|
topLeft.y = Math.max(topLeft.y - 24, 0);
|
||||||
|
setLocation(topLeft.x - LEFT_EMPTY_BORDER, topLeft.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Action performed when Escape is pressed in this popup.
|
||||||
|
*/
|
||||||
|
private class EscapeAction extends AbstractAction {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void actionPerformed(ActionEvent e) {
|
||||||
|
listener.uninstallAndHide();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Listens for events in this popup.
|
||||||
|
*/
|
||||||
|
private class Listener extends WindowAdapter implements ComponentListener {
|
||||||
|
|
||||||
|
Listener() {
|
||||||
|
|
||||||
|
addWindowFocusListener(this);
|
||||||
|
|
||||||
|
// If anything happens to the "parent" window, hide this popup
|
||||||
|
Window parent = (Window)getParent();
|
||||||
|
parent.addWindowFocusListener(this);
|
||||||
|
parent.addWindowListener(this);
|
||||||
|
parent.addComponentListener(this);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void componentResized(ComponentEvent e) {
|
||||||
|
uninstallAndHide();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void componentMoved(ComponentEvent e) {
|
||||||
|
uninstallAndHide();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void componentShown(ComponentEvent e) {
|
||||||
|
uninstallAndHide();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void componentHidden(ComponentEvent e) {
|
||||||
|
uninstallAndHide();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void windowActivated(WindowEvent e) {
|
||||||
|
checkForParentWindowEvent(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void windowLostFocus(WindowEvent e) {
|
||||||
|
uninstallAndHide();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void windowIconified(WindowEvent e) {
|
||||||
|
checkForParentWindowEvent(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean checkForParentWindowEvent(WindowEvent e) {
|
||||||
|
if (e.getSource()==getParent()) {
|
||||||
|
uninstallAndHide();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void uninstallAndHide() {
|
||||||
|
Window parent = (Window)getParent();
|
||||||
|
parent.removeWindowFocusListener(this);
|
||||||
|
parent.removeWindowListener(this);
|
||||||
|
parent.removeComponentListener(this);
|
||||||
|
removeWindowFocusListener(this);
|
||||||
|
setVisible(false);
|
||||||
|
dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
/*
|
||||||
|
* 03/09/2013
|
||||||
|
*
|
||||||
|
* OccurrenceMarker - Marks occurrences of the current token.
|
||||||
|
*
|
||||||
|
* This library is distributed under a modified BSD license. See the included
|
||||||
|
* RSyntaxTextArea.License.txt file for details.
|
||||||
|
*/
|
||||||
|
package org.fife.ui.rsyntaxtextarea;
|
||||||
|
|
||||||
|
import org.fife.ui.rtextarea.SmartHighlightPainter;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An <code>OccurrenceMarker</code> is called when the caret stops moving after
|
||||||
|
* a short period. If the current {@link TokenMaker} returns an instance of
|
||||||
|
* this class, it is told to mark all occurrences of the identifier at the
|
||||||
|
* caret position.
|
||||||
|
*
|
||||||
|
* @author Robert Futrell
|
||||||
|
* @version 1.0
|
||||||
|
*/
|
||||||
|
public interface OccurrenceMarker {
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the token to mark occurrences, of, provided it matches the
|
||||||
|
* criteria put forth by {@link #isValidType(RSyntaxTextArea, Token)}.
|
||||||
|
* For most languages, this method should return the token at the caret
|
||||||
|
* position.
|
||||||
|
*
|
||||||
|
* @param textArea The text area.
|
||||||
|
* @return The token to (possibly) mark occurrences of, or
|
||||||
|
* <code>null</code> if none.
|
||||||
|
*/
|
||||||
|
Token getTokenToMark(RSyntaxTextArea textArea);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether the specified token is a type that we can do a
|
||||||
|
* "mark occurrences" of. Typically, this will delegate to
|
||||||
|
* {@link RSyntaxTextArea#getMarkOccurrencesOfTokenType(int)}.
|
||||||
|
*
|
||||||
|
* @param textArea The text area.
|
||||||
|
* @param t The token.
|
||||||
|
* @return Whether we should mark all occurrences of this token.
|
||||||
|
*/
|
||||||
|
boolean isValidType(RSyntaxTextArea textArea, Token t);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when occurrences of a token should be marked.
|
||||||
|
*
|
||||||
|
* @param doc The document.
|
||||||
|
* @param t The document whose relevant occurrences should be marked.
|
||||||
|
* @param h The highlighter to add the highlights to.
|
||||||
|
* @param p The painter for the highlights.
|
||||||
|
*/
|
||||||
|
void markOccurrences(RSyntaxDocument doc, Token t,
|
||||||
|
RSyntaxTextAreaHighlighter h, SmartHighlightPainter p);
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,829 @@
|
|||||||
|
/*
|
||||||
|
* 09/26/2005
|
||||||
|
*
|
||||||
|
* ParserManager.java - Manages the parsing of an RSyntaxTextArea's document,
|
||||||
|
* if necessary.
|
||||||
|
*
|
||||||
|
* This library is distributed under a modified BSD license. See the included
|
||||||
|
* RSyntaxTextArea.License.txt file for details.
|
||||||
|
*/
|
||||||
|
package org.fife.ui.rsyntaxtextarea;
|
||||||
|
|
||||||
|
import java.awt.Color;
|
||||||
|
import java.awt.Point;
|
||||||
|
import java.awt.Rectangle;
|
||||||
|
import java.awt.event.ActionEvent;
|
||||||
|
import java.awt.event.ActionListener;
|
||||||
|
import java.awt.event.MouseEvent;
|
||||||
|
import java.beans.PropertyChangeEvent;
|
||||||
|
import java.beans.PropertyChangeListener;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.security.AccessControlException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import javax.swing.Timer;
|
||||||
|
import javax.swing.ToolTipManager;
|
||||||
|
import javax.swing.event.DocumentEvent;
|
||||||
|
import javax.swing.event.DocumentListener;
|
||||||
|
import javax.swing.event.HyperlinkEvent;
|
||||||
|
import javax.swing.event.HyperlinkListener;
|
||||||
|
import javax.swing.text.BadLocationException;
|
||||||
|
import javax.swing.text.Document;
|
||||||
|
import javax.swing.text.Element;
|
||||||
|
import javax.swing.text.Position;
|
||||||
|
|
||||||
|
import org.fife.ui.rsyntaxtextarea.parser.ParseResult;
|
||||||
|
import org.fife.ui.rsyntaxtextarea.parser.Parser;
|
||||||
|
import org.fife.ui.rsyntaxtextarea.parser.ParserNotice;
|
||||||
|
import org.fife.ui.rsyntaxtextarea.parser.ToolTipInfo;
|
||||||
|
import org.fife.ui.rtextarea.RDocument;
|
||||||
|
import org.fife.ui.rtextarea.RTextAreaHighlighter.HighlightInfo;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Manages running a parser object for an <code>RSyntaxTextArea</code>.
|
||||||
|
*
|
||||||
|
* @author Robert Futrell
|
||||||
|
* @version 0.9
|
||||||
|
*/
|
||||||
|
class ParserManager implements DocumentListener, ActionListener,
|
||||||
|
HyperlinkListener, PropertyChangeListener {
|
||||||
|
|
||||||
|
private RSyntaxTextArea textArea;
|
||||||
|
private List<Parser> parsers;
|
||||||
|
private Timer timer;
|
||||||
|
private boolean running;
|
||||||
|
private Parser parserForTip;
|
||||||
|
private Position firstOffsetModded;
|
||||||
|
private Position lastOffsetModded;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mapping of notices to their highlights in the editor. Can't use a Map
|
||||||
|
* since parsers could return two <code>ParserNotice</code>s that compare
|
||||||
|
* equally via <code>equals()</code>. Real-world example: The Perl
|
||||||
|
* compiler will return 2+ identical error messages if the same error is
|
||||||
|
* committed in a single line more than once.
|
||||||
|
*/
|
||||||
|
private List<NoticeHighlightPair> noticeHighlightPairs;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Painter used to underline errors.
|
||||||
|
*/
|
||||||
|
private SquiggleUnderlineHighlightPainter parserErrorHighlightPainter =
|
||||||
|
new SquiggleUnderlineHighlightPainter(Color.RED);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If this system property is set to <code>true</code>, debug messages
|
||||||
|
* will be printed to stdout to help diagnose parsing issues.
|
||||||
|
*/
|
||||||
|
private static final String PROPERTY_DEBUG_PARSING = "rsta.debugParsing";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether to print debug messages while running parsers.
|
||||||
|
*/
|
||||||
|
private static final boolean DEBUG_PARSING;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The default delay between the last key press and when the document
|
||||||
|
* is parsed, in milliseconds.
|
||||||
|
*/
|
||||||
|
private static final int DEFAULT_DELAY_MS = 1250;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
*
|
||||||
|
* @param textArea The text area whose document the parser will be
|
||||||
|
* parsing.
|
||||||
|
*/
|
||||||
|
ParserManager(RSyntaxTextArea textArea) {
|
||||||
|
this(DEFAULT_DELAY_MS, textArea);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
*
|
||||||
|
* @param delay The delay between the last key press and when the document
|
||||||
|
* is parsed.
|
||||||
|
* @param textArea The text area whose document the parser will be
|
||||||
|
* parsing.
|
||||||
|
*/
|
||||||
|
ParserManager(int delay, RSyntaxTextArea textArea) {
|
||||||
|
this.textArea = textArea;
|
||||||
|
textArea.getDocument().addDocumentListener(this);
|
||||||
|
textArea.addPropertyChangeListener("document", this);
|
||||||
|
parsers = new ArrayList<Parser>(1); // Usually small
|
||||||
|
timer = new Timer(delay, this);
|
||||||
|
timer.setRepeats(false);
|
||||||
|
running = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the timer fires (e.g. it's time to parse the document).
|
||||||
|
*
|
||||||
|
* @param e The event.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void actionPerformed(ActionEvent e) {
|
||||||
|
|
||||||
|
// Sanity check - should have >1 parser if event is fired.
|
||||||
|
int parserCount = getParserCount();
|
||||||
|
if (parserCount==0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
long begin = 0;
|
||||||
|
if (DEBUG_PARSING) {
|
||||||
|
begin = System.currentTimeMillis();
|
||||||
|
}
|
||||||
|
|
||||||
|
RSyntaxDocument doc = (RSyntaxDocument)textArea.getDocument();
|
||||||
|
|
||||||
|
Element root = doc.getDefaultRootElement();
|
||||||
|
int firstLine = firstOffsetModded==null ? 0 :
|
||||||
|
root.getElementIndex(firstOffsetModded.getOffset());
|
||||||
|
int lastLine = lastOffsetModded==null ? root.getElementCount()-1 :
|
||||||
|
root.getElementIndex(lastOffsetModded.getOffset());
|
||||||
|
firstOffsetModded = lastOffsetModded = null;
|
||||||
|
if (DEBUG_PARSING) {
|
||||||
|
System.out.println("[DEBUG]: Minimum lines to parse: " + firstLine + "-" + lastLine);
|
||||||
|
}
|
||||||
|
|
||||||
|
String style = textArea.getSyntaxEditingStyle();
|
||||||
|
doc.readLock();
|
||||||
|
try {
|
||||||
|
for (int i=0; i<parserCount; i++) {
|
||||||
|
Parser parser = getParser(i);
|
||||||
|
if (parser.isEnabled()) {
|
||||||
|
ParseResult res = parser.parse(doc, style);
|
||||||
|
addParserNoticeHighlights(res);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
clearParserNoticeHighlights(parser);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
textArea.fireParserNoticesChange();
|
||||||
|
} finally {
|
||||||
|
doc.readUnlock();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (DEBUG_PARSING) {
|
||||||
|
float time = (System.currentTimeMillis()-begin)/1000f;
|
||||||
|
System.out.println("Total parsing time: " + time + " seconds");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a parser for the text area.
|
||||||
|
*
|
||||||
|
* @param parser The new parser. If this is <code>null</code>, nothing
|
||||||
|
* happens.
|
||||||
|
* @see #getParser(int)
|
||||||
|
* @see #removeParser(Parser)
|
||||||
|
*/
|
||||||
|
public void addParser(Parser parser) {
|
||||||
|
if (parser!=null && !parsers.contains(parser)) {
|
||||||
|
if (running) {
|
||||||
|
timer.stop();
|
||||||
|
}
|
||||||
|
parsers.add(parser);
|
||||||
|
if (parsers.size()==1) {
|
||||||
|
// Okay to call more than once.
|
||||||
|
ToolTipManager.sharedInstance().registerComponent(textArea);
|
||||||
|
}
|
||||||
|
if (running) {
|
||||||
|
timer.restart();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds highlights for a list of parser notices. Any current notices
|
||||||
|
* from the same Parser, in the same parsed range, are removed.
|
||||||
|
*
|
||||||
|
* @param res The result of a parsing.
|
||||||
|
* @see #clearParserNoticeHighlights()
|
||||||
|
*/
|
||||||
|
private void addParserNoticeHighlights(ParseResult res) {
|
||||||
|
|
||||||
|
// Parsers are supposed to return at least empty ParseResults, but
|
||||||
|
// we'll be defensive here.
|
||||||
|
if (res==null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (DEBUG_PARSING) {
|
||||||
|
System.out.println("[DEBUG]: Adding parser notices from " +
|
||||||
|
res.getParser());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (noticeHighlightPairs==null) {
|
||||||
|
noticeHighlightPairs = new ArrayList<NoticeHighlightPair>();
|
||||||
|
}
|
||||||
|
|
||||||
|
removeParserNotices(res);
|
||||||
|
|
||||||
|
List<ParserNotice> notices = res.getNotices();
|
||||||
|
if (notices.size()>0) { // Guaranteed non-null
|
||||||
|
|
||||||
|
RSyntaxTextAreaHighlighter h = (RSyntaxTextAreaHighlighter)
|
||||||
|
textArea.getHighlighter();
|
||||||
|
|
||||||
|
for (ParserNotice notice : notices) {
|
||||||
|
if (DEBUG_PARSING) {
|
||||||
|
System.out.println("[DEBUG]: ... adding: " + notice);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
HighlightInfo highlight = null;
|
||||||
|
if (notice.getShowInEditor()) {
|
||||||
|
highlight = h.addParserHighlight(notice,
|
||||||
|
parserErrorHighlightPainter);
|
||||||
|
}
|
||||||
|
noticeHighlightPairs.add(new NoticeHighlightPair(notice, highlight));
|
||||||
|
} catch (BadLocationException ble) { // Never happens
|
||||||
|
ble.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if (DEBUG_PARSING) {
|
||||||
|
System.out.println("[DEBUG]: Done adding parser notices from " +
|
||||||
|
res.getParser());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the document is modified.
|
||||||
|
*
|
||||||
|
* @param e The document event.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void changedUpdate(DocumentEvent e) {
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void clearParserNoticeHighlights() {
|
||||||
|
RSyntaxTextAreaHighlighter h = (RSyntaxTextAreaHighlighter)
|
||||||
|
textArea.getHighlighter();
|
||||||
|
if (h!=null) {
|
||||||
|
h.clearParserHighlights();
|
||||||
|
}
|
||||||
|
if (noticeHighlightPairs!=null) {
|
||||||
|
noticeHighlightPairs.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes all parser notice highlights for a specific parser.
|
||||||
|
*
|
||||||
|
* @param parser The parser whose highlights to remove.
|
||||||
|
*/
|
||||||
|
private void clearParserNoticeHighlights(Parser parser) {
|
||||||
|
RSyntaxTextAreaHighlighter h = (RSyntaxTextAreaHighlighter)
|
||||||
|
textArea.getHighlighter();
|
||||||
|
if (h!=null) {
|
||||||
|
h.clearParserHighlights(parser);
|
||||||
|
}
|
||||||
|
if (noticeHighlightPairs!=null) {
|
||||||
|
Iterator<NoticeHighlightPair> i=noticeHighlightPairs.iterator();
|
||||||
|
while (i.hasNext()) {
|
||||||
|
NoticeHighlightPair pair = i.next();
|
||||||
|
if (pair.notice.getParser()==parser) {
|
||||||
|
i.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes all parsers and any highlights they have created.
|
||||||
|
*
|
||||||
|
* @see #addParser(Parser)
|
||||||
|
*/
|
||||||
|
public void clearParsers() {
|
||||||
|
timer.stop();
|
||||||
|
clearParserNoticeHighlights();
|
||||||
|
parsers.clear();
|
||||||
|
textArea.fireParserNoticesChange();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Forces the given {@link Parser} to re-parse the content of this text
|
||||||
|
* area.<p>
|
||||||
|
*
|
||||||
|
* This method can be useful when a <code>Parser</code> can be configured
|
||||||
|
* as to what notices it returns. For example, if a Java language parser
|
||||||
|
* can be configured to set whether no serialVersionUID is a warning,
|
||||||
|
* error, or ignored, this method can be called after changing the expected
|
||||||
|
* notice type to have the document re-parsed.
|
||||||
|
*
|
||||||
|
* @param parser The index of the <code>Parser</code> to re-run.
|
||||||
|
* @see #getParser(int)
|
||||||
|
*/
|
||||||
|
public void forceReparsing(int parser) {
|
||||||
|
Parser p = getParser(parser);
|
||||||
|
RSyntaxDocument doc = (RSyntaxDocument)textArea.getDocument();
|
||||||
|
String style = textArea.getSyntaxEditingStyle();
|
||||||
|
doc.readLock();
|
||||||
|
try {
|
||||||
|
if (p.isEnabled()) {
|
||||||
|
ParseResult res = p.parse(doc, style);
|
||||||
|
addParserNoticeHighlights(res);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
clearParserNoticeHighlights(p);
|
||||||
|
}
|
||||||
|
textArea.fireParserNoticesChange();
|
||||||
|
} finally {
|
||||||
|
doc.readUnlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the delay between the last "concurrent" edit and when the
|
||||||
|
* document is re-parsed.
|
||||||
|
*
|
||||||
|
* @return The delay, in milliseconds.
|
||||||
|
* @see #setDelay(int)
|
||||||
|
*/
|
||||||
|
public int getDelay() {
|
||||||
|
return timer.getDelay();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the specified parser.
|
||||||
|
*
|
||||||
|
* @param index The index of the parser.
|
||||||
|
* @return The parser.
|
||||||
|
* @see #getParserCount()
|
||||||
|
* @see #addParser(Parser)
|
||||||
|
* @see #removeParser(Parser)
|
||||||
|
*/
|
||||||
|
public Parser getParser(int index) {
|
||||||
|
return parsers.get(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the number of registered parsers.
|
||||||
|
*
|
||||||
|
* @return The number of registered parsers.
|
||||||
|
*/
|
||||||
|
public int getParserCount() {
|
||||||
|
return parsers.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a list of the current parser notices for this text area.
|
||||||
|
* This method (like most Swing methods) should only be called on the
|
||||||
|
* EDT.
|
||||||
|
*
|
||||||
|
* @return The list of notices. This will be an empty list if there are
|
||||||
|
* none.
|
||||||
|
*/
|
||||||
|
public List<ParserNotice> getParserNotices() {
|
||||||
|
List<ParserNotice> notices = new ArrayList<ParserNotice>();
|
||||||
|
if (noticeHighlightPairs!=null) {
|
||||||
|
for (NoticeHighlightPair pair : noticeHighlightPairs) {
|
||||||
|
notices.add(pair.notice);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return notices;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the tool tip to display for a mouse event at the given
|
||||||
|
* location. This method is overridden to give a registered parser a
|
||||||
|
* chance to display a tool tip (such as an error description when the
|
||||||
|
* mouse is over an error highlight).
|
||||||
|
*
|
||||||
|
* @param e The mouse event.
|
||||||
|
* @return The tool tip to display, and possibly a hyperlink event handler.
|
||||||
|
*/
|
||||||
|
public ToolTipInfo getToolTipText(MouseEvent e) {
|
||||||
|
|
||||||
|
String tip = null;
|
||||||
|
HyperlinkListener listener = null;
|
||||||
|
parserForTip = null;
|
||||||
|
Point p = e.getPoint();
|
||||||
|
|
||||||
|
// try {
|
||||||
|
int pos = textArea.viewToModel(p);
|
||||||
|
/*
|
||||||
|
Highlighter.Highlight[] highlights = textArea.getHighlighter().
|
||||||
|
getHighlights();
|
||||||
|
for (int i=0; i<highlights.length; i++) {
|
||||||
|
Highlighter.Highlight h = highlights[i];
|
||||||
|
//if (h instanceof ParserNoticeHighlight) {
|
||||||
|
// ParserNoticeHighlight pnh = (ParserNoticeHighlight)h;
|
||||||
|
int start = h.getStartOffset();
|
||||||
|
int end = h.getEndOffset();
|
||||||
|
if (start<=pos && end>=pos) {
|
||||||
|
//return pnh.getMessage();
|
||||||
|
return textArea.getText(start, end-start);
|
||||||
|
}
|
||||||
|
//}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
if (noticeHighlightPairs!=null) {
|
||||||
|
for (NoticeHighlightPair pair : noticeHighlightPairs) {
|
||||||
|
ParserNotice notice = pair.notice;
|
||||||
|
if (noticeContainsPosition(notice, pos) &&
|
||||||
|
noticeContainsPointInView(notice, p)) {
|
||||||
|
tip = notice.getToolTipText();
|
||||||
|
parserForTip = notice.getParser();
|
||||||
|
if (parserForTip instanceof HyperlinkListener) {
|
||||||
|
listener = (HyperlinkListener)parserForTip;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// } catch (BadLocationException ble) {
|
||||||
|
// ble.printStackTrace(); // Should never happen.
|
||||||
|
// }
|
||||||
|
|
||||||
|
URL imageBase = parserForTip==null ? null : parserForTip.getImageBase();
|
||||||
|
return new ToolTipInfo(tip, listener, imageBase);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the document is modified.
|
||||||
|
*
|
||||||
|
* @param e The document event.
|
||||||
|
*/
|
||||||
|
public void handleDocumentEvent(DocumentEvent e) {
|
||||||
|
if (running && parsers.size()>0) {
|
||||||
|
timer.restart();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the user clicks a hyperlink in a
|
||||||
|
* {@link org.fife.ui.rsyntaxtextarea.focusabletip.FocusableTip}.
|
||||||
|
*
|
||||||
|
* @param e The event.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void hyperlinkUpdate(HyperlinkEvent e) {
|
||||||
|
if (parserForTip!=null && parserForTip.getHyperlinkListener()!=null) {
|
||||||
|
parserForTip.getHyperlinkListener().linkClicked(textArea, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the document is modified.
|
||||||
|
*
|
||||||
|
* @param e The document event.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void insertUpdate(DocumentEvent e) {
|
||||||
|
|
||||||
|
// Keep track of the first and last offset modified. Some parsers are
|
||||||
|
// smart and will only re-parse this section of the file.
|
||||||
|
try {
|
||||||
|
int offs = e.getOffset();
|
||||||
|
if (firstOffsetModded==null || offs<firstOffsetModded.getOffset()) {
|
||||||
|
firstOffsetModded = e.getDocument().createPosition(offs);
|
||||||
|
}
|
||||||
|
offs = e.getOffset() + e.getLength();
|
||||||
|
if (lastOffsetModded==null || offs>lastOffsetModded.getOffset()) {
|
||||||
|
lastOffsetModded = e.getDocument().createPosition(offs);
|
||||||
|
}
|
||||||
|
} catch (BadLocationException ble) {
|
||||||
|
ble.printStackTrace(); // Shouldn't happen
|
||||||
|
}
|
||||||
|
|
||||||
|
handleDocumentEvent(e);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether a parser notice contains the specified offset.
|
||||||
|
*
|
||||||
|
* @param notice The notice.
|
||||||
|
* @param offs The offset.
|
||||||
|
* @return Whether the notice contains the offset.
|
||||||
|
*/
|
||||||
|
private boolean noticeContainsPosition(ParserNotice notice, int offs){
|
||||||
|
if (notice.getKnowsOffsetAndLength()) {
|
||||||
|
return notice.containsPosition(offs);
|
||||||
|
}
|
||||||
|
Document doc = textArea.getDocument();
|
||||||
|
Element root = doc.getDefaultRootElement();
|
||||||
|
int line = notice.getLine();
|
||||||
|
if (line<0) { // Defensive against possible bad user-defined notices.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Element elem = root.getElement(line);
|
||||||
|
return elem != null && offs>=elem.getStartOffset() && offs<elem.getEndOffset();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Since <code>viewToModel()</code> returns the <em>closest</em> model
|
||||||
|
* position, and the position doesn't <em>necessarily</em> contain the
|
||||||
|
* point passed in as an argument, this method checks whether the point is
|
||||||
|
* indeed contained in the view rectangle for the specified offset.
|
||||||
|
*
|
||||||
|
* @param notice The parser notice.
|
||||||
|
* @param p The point possibly contained in the view range of the
|
||||||
|
* parser notice.
|
||||||
|
* @return Whether the parser notice actually contains the specified point
|
||||||
|
* in the view.
|
||||||
|
*/
|
||||||
|
private boolean noticeContainsPointInView(ParserNotice notice,
|
||||||
|
Point p) {
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
int start, end;
|
||||||
|
if (notice.getKnowsOffsetAndLength()) {
|
||||||
|
start = notice.getOffset();
|
||||||
|
end = start + notice.getLength() - 1;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Document doc = textArea.getDocument();
|
||||||
|
Element root = doc.getDefaultRootElement();
|
||||||
|
int line = notice.getLine();
|
||||||
|
// Defend against possible bad user-defined notices.
|
||||||
|
if (line<0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Element elem = root.getElement(line);
|
||||||
|
start = elem.getStartOffset();
|
||||||
|
end = elem.getEndOffset() - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle r1 = textArea.modelToView(start);
|
||||||
|
Rectangle r2 = textArea.modelToView(end);
|
||||||
|
if (r1.y!=r2.y) {
|
||||||
|
// If the notice spans multiple lines, give them the benefit
|
||||||
|
// of the doubt. This is only "wrong" if the user is in empty
|
||||||
|
// space "to the right" of the error marker when it ends at the
|
||||||
|
// end of a line anyway.
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
r1.y--; // Be a tiny bit lenient.
|
||||||
|
r1.height += 2; // Ditto
|
||||||
|
return p.x>=r1.x && p.x<(r2.x+r2.width) &&
|
||||||
|
p.y>=r1.y && p.y<(r1.y+r1.height);
|
||||||
|
|
||||||
|
} catch (BadLocationException ble) { // Never occurs
|
||||||
|
// Give them the benefit of the doubt, should 99% of the time be
|
||||||
|
// true anyway
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when a property we're interested in changes.
|
||||||
|
*
|
||||||
|
* @param e The property change event.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void propertyChange(PropertyChangeEvent e) {
|
||||||
|
|
||||||
|
String name = e.getPropertyName();
|
||||||
|
|
||||||
|
if ("document".equals(name)) {
|
||||||
|
// The document switched out from under us
|
||||||
|
RDocument old = (RDocument)e.getOldValue();
|
||||||
|
if (old != null) {
|
||||||
|
old.removeDocumentListener(this);
|
||||||
|
}
|
||||||
|
RDocument newDoc = (RDocument)e.getNewValue();
|
||||||
|
if (newDoc != null) {
|
||||||
|
newDoc.addDocumentListener(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes a parser.
|
||||||
|
*
|
||||||
|
* @param parser The parser to remove.
|
||||||
|
* @return Whether the parser was found.
|
||||||
|
* @see #addParser(Parser)
|
||||||
|
* @see #getParser(int)
|
||||||
|
*/
|
||||||
|
public boolean removeParser(Parser parser) {
|
||||||
|
removeParserNotices(parser);
|
||||||
|
boolean removed = parsers.remove(parser);
|
||||||
|
if (removed) {
|
||||||
|
textArea.fireParserNoticesChange();
|
||||||
|
}
|
||||||
|
return removed;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes all parser notices (and clears highlights in the editor) from
|
||||||
|
* a particular parser.
|
||||||
|
*
|
||||||
|
* @param parser The parser.
|
||||||
|
*/
|
||||||
|
private void removeParserNotices(Parser parser) {
|
||||||
|
if (noticeHighlightPairs!=null) {
|
||||||
|
RSyntaxTextAreaHighlighter h = (RSyntaxTextAreaHighlighter)
|
||||||
|
textArea.getHighlighter();
|
||||||
|
for (Iterator<NoticeHighlightPair> i=noticeHighlightPairs.iterator(); i.hasNext();) {
|
||||||
|
NoticeHighlightPair pair = i.next();
|
||||||
|
if (pair.notice.getParser()==parser && pair.highlight!=null) {
|
||||||
|
h.removeParserHighlight(pair.highlight);
|
||||||
|
i.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes any currently stored notices (and the corresponding highlights
|
||||||
|
* from the editor) from the same Parser, and in the given line range,
|
||||||
|
* as in the results.
|
||||||
|
*
|
||||||
|
* @param res The results.
|
||||||
|
*/
|
||||||
|
private void removeParserNotices(ParseResult res) {
|
||||||
|
if (noticeHighlightPairs!=null) {
|
||||||
|
RSyntaxTextAreaHighlighter h = (RSyntaxTextAreaHighlighter)
|
||||||
|
textArea.getHighlighter();
|
||||||
|
for (Iterator<NoticeHighlightPair> i=noticeHighlightPairs.iterator(); i.hasNext();) {
|
||||||
|
NoticeHighlightPair pair = i.next();
|
||||||
|
boolean removed = false;
|
||||||
|
if (shouldRemoveNotice(pair.notice, res)) {
|
||||||
|
if (pair.highlight!=null) {
|
||||||
|
h.removeParserHighlight(pair.highlight);
|
||||||
|
}
|
||||||
|
i.remove();
|
||||||
|
removed = true;
|
||||||
|
}
|
||||||
|
if (DEBUG_PARSING) {
|
||||||
|
String text = removed ? "[DEBUG]: ... notice removed: " :
|
||||||
|
"[DEBUG]: ... notice not removed: ";
|
||||||
|
System.out.println(text + pair.notice);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the document is modified.
|
||||||
|
*
|
||||||
|
* @param e The document event.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void removeUpdate(DocumentEvent e) {
|
||||||
|
|
||||||
|
// Keep track of the first and last offset modified. Some parsers are
|
||||||
|
// smart and will only re-parse this section of the file. Note that
|
||||||
|
// for removals, only the line at the removal start needs to be
|
||||||
|
// re-parsed.
|
||||||
|
try {
|
||||||
|
int offs = e.getOffset();
|
||||||
|
if (firstOffsetModded==null || offs<firstOffsetModded.getOffset()) {
|
||||||
|
firstOffsetModded = e.getDocument().createPosition(offs);
|
||||||
|
}
|
||||||
|
if (lastOffsetModded==null || offs>lastOffsetModded.getOffset()) {
|
||||||
|
lastOffsetModded = e.getDocument().createPosition(offs);
|
||||||
|
}
|
||||||
|
} catch (BadLocationException ble) { // Never happens
|
||||||
|
ble.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
handleDocumentEvent(e);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Restarts parsing the document.
|
||||||
|
*
|
||||||
|
* @see #stopParsing()
|
||||||
|
*/
|
||||||
|
public void restartParsing() {
|
||||||
|
timer.restart();
|
||||||
|
running = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the delay between the last "concurrent" edit and when the document
|
||||||
|
* is re-parsed.
|
||||||
|
*
|
||||||
|
* @param millis The new delay, in milliseconds. This must be greater
|
||||||
|
* than <code>0</code>.
|
||||||
|
* @see #getDelay()
|
||||||
|
*/
|
||||||
|
public void setDelay(int millis) {
|
||||||
|
if (running) {
|
||||||
|
timer.stop();
|
||||||
|
}
|
||||||
|
timer.setInitialDelay(millis);
|
||||||
|
timer.setDelay(millis);
|
||||||
|
if (running) {
|
||||||
|
timer.start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether a parser notice should be removed, based on a parse
|
||||||
|
* result.
|
||||||
|
*
|
||||||
|
* @param notice The notice in question.
|
||||||
|
* @param res The result.
|
||||||
|
* @return Whether the notice should be removed.
|
||||||
|
*/
|
||||||
|
private boolean shouldRemoveNotice(ParserNotice notice,
|
||||||
|
ParseResult res) {
|
||||||
|
|
||||||
|
if (DEBUG_PARSING) {
|
||||||
|
System.out.println("[DEBUG]: ... ... shouldRemoveNotice " +
|
||||||
|
notice + ": " + (notice.getParser()==res.getParser()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE: We must currently remove all notices for the parser. Parser
|
||||||
|
// implementors are required to parse the entire document each parsing
|
||||||
|
// request, as RSTA is not yet sophisticated enough to determine the
|
||||||
|
// minimum range of text to parse (and ParserNotices' locations aren't
|
||||||
|
// updated when the Document is mutated, which would be a requirement
|
||||||
|
// for this as well).
|
||||||
|
// return same_parser && (in_reparsed_range || in_deleted_end_of_doc)
|
||||||
|
return notice.getParser()==res.getParser();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stops parsing the document.
|
||||||
|
*
|
||||||
|
* @see #restartParsing()
|
||||||
|
*/
|
||||||
|
public void stopParsing() {
|
||||||
|
timer.stop();
|
||||||
|
running = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mapping of a parser notice to its highlight in the editor.
|
||||||
|
*/
|
||||||
|
private static class NoticeHighlightPair {
|
||||||
|
|
||||||
|
private ParserNotice notice;
|
||||||
|
private HighlightInfo highlight;
|
||||||
|
|
||||||
|
NoticeHighlightPair(ParserNotice notice, HighlightInfo highlight) {
|
||||||
|
this.notice = notice;
|
||||||
|
this.highlight = highlight;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static {
|
||||||
|
boolean debugParsing = false;
|
||||||
|
try {
|
||||||
|
debugParsing = Boolean.getBoolean(PROPERTY_DEBUG_PARSING);
|
||||||
|
} catch (AccessControlException ace) {
|
||||||
|
// Likely an applet's security manager.
|
||||||
|
debugParsing = false; // FindBugs
|
||||||
|
}
|
||||||
|
DEBUG_PARSING = debugParsing;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
/*
|
||||||
|
* 01/11/2011
|
||||||
|
*
|
||||||
|
* PopupWindowDecorator.java - Hook allowing hosting applications to decorate
|
||||||
|
* JWindows created by the AutoComplete library.
|
||||||
|
*
|
||||||
|
* This library is distributed under a modified BSD license. See the included
|
||||||
|
* RSyntaxTextArea.License.txt file for details.
|
||||||
|
*/
|
||||||
|
package org.fife.ui.rsyntaxtextarea;
|
||||||
|
|
||||||
|
import javax.swing.JWindow;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A hook allowing hosting applications to decorate JWindows created by the
|
||||||
|
* AutoComplete library. For example, you could use the
|
||||||
|
* <a href="http://jgoodies.com/">JGoodies</a> library to add drop shadows
|
||||||
|
* to the windows.
|
||||||
|
*
|
||||||
|
* @author Robert Futrell
|
||||||
|
* @version 1.0
|
||||||
|
*/
|
||||||
|
public abstract class PopupWindowDecorator {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The singleton instance of this class.
|
||||||
|
*/
|
||||||
|
private static PopupWindowDecorator decorator;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback called whenever an appropriate JWindow is created by the
|
||||||
|
* AutoComplete library. Implementations can decorate the window however
|
||||||
|
* they see fit.
|
||||||
|
*
|
||||||
|
* @param window The newly-created window.
|
||||||
|
*/
|
||||||
|
public abstract void decorate(JWindow window);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the singleton instance of this class. This should only be
|
||||||
|
* called on the EDT.
|
||||||
|
*
|
||||||
|
* @return The singleton instance of this class, or <code>null</code>
|
||||||
|
* for none.
|
||||||
|
* @see #set(PopupWindowDecorator)
|
||||||
|
*/
|
||||||
|
public static PopupWindowDecorator get() {
|
||||||
|
return decorator;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the singleton instance of this class. This should only be called
|
||||||
|
* on the EDT.
|
||||||
|
*
|
||||||
|
* @param decorator The new instance of this class. This may be
|
||||||
|
* <code>null</code>.
|
||||||
|
* @see #get()
|
||||||
|
*/
|
||||||
|
public static void set(PopupWindowDecorator decorator) {
|
||||||
|
PopupWindowDecorator.decorator = decorator;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
/*
|
||||||
|
* 02/10/2009
|
||||||
|
*
|
||||||
|
* RSTAView.java - An <code>RSyntaxTextArea</code> view.
|
||||||
|
*
|
||||||
|
* This library is distributed under a modified BSD license. See the included
|
||||||
|
* RSyntaxTextArea.License.txt file for details.
|
||||||
|
*/
|
||||||
|
package org.fife.ui.rsyntaxtextarea;
|
||||||
|
|
||||||
|
import java.awt.Rectangle;
|
||||||
|
|
||||||
|
import javax.swing.text.BadLocationException;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility methods for RSyntaxTextArea's views.
|
||||||
|
*
|
||||||
|
* @author Robert Futrell
|
||||||
|
* @version 1.0
|
||||||
|
*/
|
||||||
|
interface RSTAView {
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the y-coordinate of the specified line.<p>
|
||||||
|
*
|
||||||
|
* This method is quicker than using traditional
|
||||||
|
* <code>modelToView(int)</code> calls, as the entire bounding box isn't
|
||||||
|
* computed.
|
||||||
|
*
|
||||||
|
* @param alloc The area the text area can render into.
|
||||||
|
* @param line The line number.
|
||||||
|
* @return The y-coordinate of the top of the line, or <code>-1</code> if
|
||||||
|
* this text area doesn't yet have a positive size or the line is
|
||||||
|
* hidden (i.e. from folding).
|
||||||
|
* @throws BadLocationException If <code>line</code> isn't a valid line
|
||||||
|
* number for this document.
|
||||||
|
*/
|
||||||
|
int yForLine(Rectangle alloc, int line) throws BadLocationException;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the y-coordinate of the line containing a specified offset.<p>
|
||||||
|
*
|
||||||
|
* This method is quicker than using traditional
|
||||||
|
* <code>modelToView(int)</code> calls, as the entire bounding box isn't
|
||||||
|
* computed.
|
||||||
|
*
|
||||||
|
* @param alloc The area the text area can render into.
|
||||||
|
* @param offs The offset info the document.
|
||||||
|
* @return The y-coordinate of the top of the offset, or <code>-1</code> if
|
||||||
|
* this text area doesn't yet have a positive size or the line is
|
||||||
|
* hidden (i.e. from folding).
|
||||||
|
* @throws BadLocationException If <code>offs</code> isn't a valid offset
|
||||||
|
* into the document.
|
||||||
|
*/
|
||||||
|
int yForLineContaining(Rectangle alloc, int offs)
|
||||||
|
throws BadLocationException;
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,672 @@
|
|||||||
|
/*
|
||||||
|
* 10/16/2004
|
||||||
|
*
|
||||||
|
* RSyntaxDocument.java - A document capable of syntax highlighting, used by
|
||||||
|
* RSyntaxTextArea.
|
||||||
|
*
|
||||||
|
* This library is distributed under a modified BSD license. See the included
|
||||||
|
* RSyntaxTextArea.License.txt file for details.
|
||||||
|
*/
|
||||||
|
package org.fife.ui.rsyntaxtextarea;
|
||||||
|
|
||||||
|
import java.awt.event.ActionEvent;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.ObjectInputStream;
|
||||||
|
import java.util.Iterator;
|
||||||
|
|
||||||
|
import javax.swing.Action;
|
||||||
|
import javax.swing.event.DocumentEvent;
|
||||||
|
import javax.swing.text.BadLocationException;
|
||||||
|
import javax.swing.text.Element;
|
||||||
|
import javax.swing.text.Segment;
|
||||||
|
|
||||||
|
import org.fife.ui.rsyntaxtextarea.modes.AbstractMarkupTokenMaker;
|
||||||
|
import org.fife.ui.rtextarea.RDocument;
|
||||||
|
import org.fife.util.DynamicIntArray;
|
||||||
|
|
||||||
|
import me.ByteEdit.main.JavaAssemblyTokenMaker;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The document used by {@link org.fife.ui.rsyntaxtextarea.RSyntaxTextArea}.
|
||||||
|
* This document is like <code>javax.swing.text.PlainDocument</code> except that
|
||||||
|
* it also keeps track of syntax highlighting in the document. It has a "style"
|
||||||
|
* attribute associated with it that determines how syntax highlighting is done
|
||||||
|
* (i.e., what language is being highlighted).
|
||||||
|
* <p>
|
||||||
|
*
|
||||||
|
* Instances of <code>RSyntaxTextArea</code> will only accept instances of
|
||||||
|
* <code>RSyntaxDocument</code>, since it is this document that keeps track of
|
||||||
|
* syntax highlighting. All others will cause an exception to be thrown.
|
||||||
|
* <p>
|
||||||
|
*
|
||||||
|
* To change the language being syntax highlighted at any time, you merely have
|
||||||
|
* to call {@link #setSyntaxStyle}. Other than that, this document can be
|
||||||
|
* treated like any other save one caveat: all <code>DocumentEvent</code>s of
|
||||||
|
* type <code>CHANGE</code> use their offset and length values to represent the
|
||||||
|
* first and last lines, respectively, that have had their syntax coloring
|
||||||
|
* change. This is really a hack to increase the speed of the painting code and
|
||||||
|
* should really be corrected, but oh well.
|
||||||
|
*
|
||||||
|
* @author Robert Futrell
|
||||||
|
* @version 1.0
|
||||||
|
*/
|
||||||
|
public class RSyntaxDocument extends RDocument implements Iterable<Token>, SyntaxConstants {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a {@link TokenMaker} appropriate for a given programming language.
|
||||||
|
*/
|
||||||
|
private transient TokenMakerFactory tokenMakerFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Splits text into tokens for the current programming language.
|
||||||
|
*/
|
||||||
|
private transient TokenMaker tokenMaker;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The current syntax style. Only cached to keep this class serializable.
|
||||||
|
*/
|
||||||
|
private String syntaxStyle;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Array of values representing the "last token type" on each line. This is used
|
||||||
|
* in cases such as multi-line comments: if the previous line ended with an
|
||||||
|
* (unclosed) multi-line comment, we can use this knowledge and start the
|
||||||
|
* current line's syntax highlighting in multi-line comment state.
|
||||||
|
*/
|
||||||
|
protected transient DynamicIntArray lastTokensOnLines;
|
||||||
|
|
||||||
|
private transient int lastLine = -1;
|
||||||
|
private transient Token cachedTokenList;
|
||||||
|
private transient int useCacheCount = 0;
|
||||||
|
private transient int tokenRetrievalCount = 0;
|
||||||
|
|
||||||
|
private transient Segment s;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If this is set to <code>true</code>, debug information about how much token
|
||||||
|
* caching is helping is printed to stdout.
|
||||||
|
*/
|
||||||
|
private static final boolean DEBUG_TOKEN_CACHING = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a plain text document. A default root element is created, and the
|
||||||
|
* tab size set to 5.
|
||||||
|
*
|
||||||
|
* @param syntaxStyle
|
||||||
|
* The syntax highlighting scheme to use.
|
||||||
|
*/
|
||||||
|
public RSyntaxDocument(String syntaxStyle) {
|
||||||
|
this(null, syntaxStyle);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a plain text document. A default root element is created, and the
|
||||||
|
* tab size set to 5.
|
||||||
|
*
|
||||||
|
* @param tmf
|
||||||
|
* The <code>TokenMakerFactory</code> for this document. If this is
|
||||||
|
* <code>null</code>, a default factory is used.
|
||||||
|
* @param syntaxStyle
|
||||||
|
* The syntax highlighting scheme to use.
|
||||||
|
*/
|
||||||
|
public RSyntaxDocument(TokenMakerFactory tmf, String syntaxStyle) {
|
||||||
|
putProperty(tabSizeAttribute, Integer.valueOf(5));
|
||||||
|
lastTokensOnLines = new DynamicIntArray(400);
|
||||||
|
lastTokensOnLines.add(Token.NULL); // Initial (empty) line.
|
||||||
|
s = new Segment();
|
||||||
|
setTokenMakerFactory(tmf);
|
||||||
|
setSyntaxStyle(syntaxStyle);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Alerts all listeners to this document of an insertion. This is overridden so
|
||||||
|
* we can update our syntax highlighting stuff.
|
||||||
|
* <p>
|
||||||
|
* The syntax highlighting stuff has to be here instead of in
|
||||||
|
* <code>insertUpdate</code> because <code>insertUpdate</code> is not called by
|
||||||
|
* the undo/redo actions, but this method is.
|
||||||
|
*
|
||||||
|
* @param e
|
||||||
|
* The change.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected void fireInsertUpdate(DocumentEvent e) {
|
||||||
|
|
||||||
|
cachedTokenList = null;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Now that the text is actually inserted into the content and element
|
||||||
|
* structure, we can update our token elements and "last tokens on lines"
|
||||||
|
* structure.
|
||||||
|
*/
|
||||||
|
|
||||||
|
Element lineMap = getDefaultRootElement();
|
||||||
|
DocumentEvent.ElementChange change = e.getChange(lineMap);
|
||||||
|
Element[] added = change == null ? null : change.getChildrenAdded();
|
||||||
|
|
||||||
|
int numLines = lineMap.getElementCount();
|
||||||
|
int line = lineMap.getElementIndex(e.getOffset());
|
||||||
|
int previousLine = line - 1;
|
||||||
|
int previousTokenType = (previousLine > -1 ? lastTokensOnLines.get(previousLine) : Token.NULL);
|
||||||
|
|
||||||
|
// If entire lines were added...
|
||||||
|
if (added != null && added.length > 0) {
|
||||||
|
|
||||||
|
Element[] removed = change.getChildrenRemoved();
|
||||||
|
int numRemoved = removed != null ? removed.length : 0;
|
||||||
|
|
||||||
|
int endBefore = line + added.length - numRemoved;
|
||||||
|
// System.err.println("... adding lines: " + line + " - " + (endBefore-1));
|
||||||
|
// System.err.println("... ... added: " + added.length + ", removed:" +
|
||||||
|
// numRemoved);
|
||||||
|
for (int i = line; i < endBefore; i++) {
|
||||||
|
|
||||||
|
setSharedSegment(i); // Loads line i's text into s.
|
||||||
|
|
||||||
|
int tokenType = tokenMaker.getLastTokenTypeOnLine(s, previousTokenType);
|
||||||
|
lastTokensOnLines.add(i, tokenType);
|
||||||
|
// System.err.println("--------- lastTokensOnLines.size() == " +
|
||||||
|
// lastTokensOnLines.getSize());
|
||||||
|
|
||||||
|
previousTokenType = tokenType;
|
||||||
|
|
||||||
|
} // End of for (int i=line; i<endBefore; i++).
|
||||||
|
|
||||||
|
// Update last tokens for lines below until they stop changing.
|
||||||
|
updateLastTokensBelow(endBefore, numLines, previousTokenType);
|
||||||
|
|
||||||
|
} // End of if (added!=null && added.length>0).
|
||||||
|
|
||||||
|
// Otherwise, text was inserted on a single line...
|
||||||
|
else {
|
||||||
|
|
||||||
|
// Update last tokens for lines below until they stop changing.
|
||||||
|
updateLastTokensBelow(line, numLines, previousTokenType);
|
||||||
|
|
||||||
|
} // End of else.
|
||||||
|
|
||||||
|
// Let all listeners know about the insertion.
|
||||||
|
super.fireInsertUpdate(e);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method is called AFTER the content has been inserted into the document
|
||||||
|
* and the element structure has been updated.
|
||||||
|
* <p>
|
||||||
|
* The syntax-highlighting updates need to be done here (as opposed to an
|
||||||
|
* override of <code>postRemoveUpdate</code>) as this method is called in
|
||||||
|
* response to undo/redo events, whereas <code>postRemoveUpdate</code> is not.
|
||||||
|
* <p>
|
||||||
|
* Now that the text is actually inserted into the content and element
|
||||||
|
* structure, we can update our token elements and "last tokens on lines"
|
||||||
|
* structure.
|
||||||
|
*
|
||||||
|
* @param chng
|
||||||
|
* The change that occurred.
|
||||||
|
* @see #removeUpdate
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected void fireRemoveUpdate(DocumentEvent chng) {
|
||||||
|
|
||||||
|
cachedTokenList = null;
|
||||||
|
Element lineMap = getDefaultRootElement();
|
||||||
|
int numLines = lineMap.getElementCount();
|
||||||
|
|
||||||
|
DocumentEvent.ElementChange change = chng.getChange(lineMap);
|
||||||
|
Element[] removed = change == null ? null : change.getChildrenRemoved();
|
||||||
|
|
||||||
|
// If entire lines were removed...
|
||||||
|
if (removed != null && removed.length > 0) {
|
||||||
|
|
||||||
|
int line = change.getIndex(); // First line entirely removed.
|
||||||
|
int previousLine = line - 1; // Line before that.
|
||||||
|
int previousTokenType = (previousLine > -1 ? lastTokensOnLines.get(previousLine) : Token.NULL);
|
||||||
|
|
||||||
|
Element[] added = change.getChildrenAdded();
|
||||||
|
int numAdded = added == null ? 0 : added.length;
|
||||||
|
|
||||||
|
// Remove the cached last-token values for the removed lines.
|
||||||
|
int endBefore = line + removed.length - numAdded;
|
||||||
|
// System.err.println("... removing lines: " + line + " - " + (endBefore-1));
|
||||||
|
// System.err.println("... added: " + numAdded + ", removed: " +
|
||||||
|
// removed.length);
|
||||||
|
|
||||||
|
lastTokensOnLines.removeRange(line, endBefore); // Removing values for lines [line-(endBefore-1)].
|
||||||
|
// System.err.println("--------- lastTokensOnLines.size() == " +
|
||||||
|
// lastTokensOnLines.getSize());
|
||||||
|
|
||||||
|
// Update last tokens for lines below until they've stopped changing.
|
||||||
|
updateLastTokensBelow(line, numLines, previousTokenType);
|
||||||
|
|
||||||
|
} // End of if (removed!=null && removed.size()>0).
|
||||||
|
|
||||||
|
// Otherwise, text was removed from just one line...
|
||||||
|
else {
|
||||||
|
|
||||||
|
int line = lineMap.getElementIndex(chng.getOffset());
|
||||||
|
if (line >= lastTokensOnLines.getSize()) {
|
||||||
|
return; // If we're editing the last line in a document...
|
||||||
|
}
|
||||||
|
|
||||||
|
int previousLine = line - 1;
|
||||||
|
int previousTokenType = (previousLine > -1 ? lastTokensOnLines.get(previousLine) : Token.NULL);
|
||||||
|
// System.err.println("previousTokenType for line : " + previousLine + " is " +
|
||||||
|
// previousTokenType);
|
||||||
|
// Update last tokens for lines below until they've stopped changing.
|
||||||
|
updateLastTokensBelow(line, numLines, previousTokenType);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Let all of our listeners know about the removal.
|
||||||
|
super.fireRemoveUpdate(chng);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the closest {@link TokenTypes "standard" token type} for a given
|
||||||
|
* "internal" token type (e.g. one whose value is <code>< 0</code>).
|
||||||
|
*
|
||||||
|
* @param type
|
||||||
|
* The token type.
|
||||||
|
* @return The closest "standard" token type. If a mapping is not defined for
|
||||||
|
* this language, then <code>type</code> is returned.
|
||||||
|
*/
|
||||||
|
public int getClosestStandardTokenTypeForInternalType(int type) {
|
||||||
|
return tokenMaker.getClosestStandardTokenTypeForInternalType(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether closing markup tags should be automatically completed. This
|
||||||
|
* method only returns <code>true</code> if {@link #getLanguageIsMarkup()} also
|
||||||
|
* returns <code>true</code>.
|
||||||
|
*
|
||||||
|
* @return Whether markup closing tags should be automatically completed.
|
||||||
|
* @see #getLanguageIsMarkup()
|
||||||
|
*/
|
||||||
|
public boolean getCompleteMarkupCloseTags() {
|
||||||
|
// TODO: Remove terrible dependency on AbstractMarkupTokenMaker
|
||||||
|
return getLanguageIsMarkup() && ((AbstractMarkupTokenMaker) tokenMaker).getCompleteCloseTags();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether the current programming language uses curly braces
|
||||||
|
* ('<code>{</code>' and '<code>}</code>') to denote code blocks.
|
||||||
|
*
|
||||||
|
* @param languageIndex
|
||||||
|
* The language index at the offset in question. Since some
|
||||||
|
* <code>TokenMaker</code>s effectively have nested languages (such
|
||||||
|
* as JavaScript in HTML), this parameter tells the
|
||||||
|
* <code>TokenMaker</code> what sub-language to look at.
|
||||||
|
* @return Whether curly braces denote code blocks.
|
||||||
|
*/
|
||||||
|
public boolean getCurlyBracesDenoteCodeBlocks(int languageIndex) {
|
||||||
|
return tokenMaker.getCurlyBracesDenoteCodeBlocks(languageIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether the current language is a markup language, such as HTML, XML
|
||||||
|
* or PHP.
|
||||||
|
*
|
||||||
|
* @return Whether the current language is a markup language.
|
||||||
|
*/
|
||||||
|
public boolean getLanguageIsMarkup() {
|
||||||
|
return tokenMaker.isMarkupLanguage();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the token type of the last token on the given line.
|
||||||
|
*
|
||||||
|
* @param line
|
||||||
|
* The line to inspect.
|
||||||
|
* @return The token type of the last token on the specified line. If the line
|
||||||
|
* is invalid, an exception is thrown.
|
||||||
|
*/
|
||||||
|
public int getLastTokenTypeOnLine(int line) {
|
||||||
|
return lastTokensOnLines.get(line);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the text to place at the beginning and end of a line to "comment" it
|
||||||
|
* in this programming language.
|
||||||
|
*
|
||||||
|
* @return The start and end strings to add to a line to "comment" it out. A
|
||||||
|
* <code>null</code> value for either means there is no string to add
|
||||||
|
* for that part. A value of <code>null</code> for the array means this
|
||||||
|
* language does not support commenting/uncommenting lines.
|
||||||
|
*/
|
||||||
|
public String[] getLineCommentStartAndEnd(int languageIndex) {
|
||||||
|
return tokenMaker.getLineCommentStartAndEnd(languageIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether tokens of the specified type should have "mark occurrences"
|
||||||
|
* enabled for the current programming language.
|
||||||
|
*
|
||||||
|
* @param type
|
||||||
|
* The token type.
|
||||||
|
* @return Whether tokens of this type should have "mark occurrences" enabled.
|
||||||
|
*/
|
||||||
|
boolean getMarkOccurrencesOfTokenType(int type) {
|
||||||
|
return tokenMaker.getMarkOccurrencesOfTokenType(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the occurrence marker for the current language.
|
||||||
|
*
|
||||||
|
* @return The occurrence marker.
|
||||||
|
*/
|
||||||
|
OccurrenceMarker getOccurrenceMarker() {
|
||||||
|
return tokenMaker.getOccurrenceMarker();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method returns whether auto indentation should be done if Enter is
|
||||||
|
* pressed at the end of the specified line.
|
||||||
|
*
|
||||||
|
* @param line
|
||||||
|
* The line to check.
|
||||||
|
* @return Whether an extra indentation should be done.
|
||||||
|
*/
|
||||||
|
public boolean getShouldIndentNextLine(int line) {
|
||||||
|
Token t = getTokenListForLine(line);
|
||||||
|
t = t.getLastNonCommentNonWhitespaceToken();
|
||||||
|
return tokenMaker.getShouldIndentNextLineAfter(t);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the syntax style being used.
|
||||||
|
*
|
||||||
|
* @return The syntax style.
|
||||||
|
* @see #setSyntaxStyle(String)
|
||||||
|
*/
|
||||||
|
public String getSyntaxStyle() {
|
||||||
|
return syntaxStyle;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a token list for the specified segment of text representing the
|
||||||
|
* specified line number. This method is basically a wrapper for
|
||||||
|
* <code>tokenMaker.getTokenList</code> that takes into account the last token
|
||||||
|
* on the previous line to assure token accuracy.
|
||||||
|
*
|
||||||
|
* @param line
|
||||||
|
* The line number of <code>text</code> in the document, >= 0.
|
||||||
|
* @return A token list representing the specified line.
|
||||||
|
*/
|
||||||
|
public final Token getTokenListForLine(int line) {
|
||||||
|
|
||||||
|
tokenRetrievalCount++;
|
||||||
|
if (line == lastLine && cachedTokenList != null) {
|
||||||
|
if (DEBUG_TOKEN_CACHING) {
|
||||||
|
useCacheCount++;
|
||||||
|
System.err.println("--- Using cached line; ratio now: " + useCacheCount + "/" + tokenRetrievalCount);
|
||||||
|
}
|
||||||
|
return cachedTokenList;
|
||||||
|
}
|
||||||
|
lastLine = line;
|
||||||
|
|
||||||
|
Element map = getDefaultRootElement();
|
||||||
|
Element elem = map.getElement(line);
|
||||||
|
int startOffset = elem.getStartOffset();
|
||||||
|
// int endOffset = (line==map.getElementCount()-1 ? elem.getEndOffset() - 1:
|
||||||
|
// elem.getEndOffset() - 1);
|
||||||
|
int endOffset = elem.getEndOffset() - 1; // Why always "-1"?
|
||||||
|
try {
|
||||||
|
getText(startOffset, endOffset - startOffset, s);
|
||||||
|
} catch (BadLocationException ble) {
|
||||||
|
ble.printStackTrace();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
int initialTokenType = line == 0 ? Token.NULL : getLastTokenTypeOnLine(line - 1);
|
||||||
|
|
||||||
|
// return tokenMaker.getTokenList(s, initialTokenType, startOffset);
|
||||||
|
cachedTokenList = tokenMaker.getTokenList(s, initialTokenType, startOffset);
|
||||||
|
return cachedTokenList;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean insertBreakSpecialHandling(ActionEvent e) {
|
||||||
|
Action a = tokenMaker.getInsertBreakAction();
|
||||||
|
if (a != null) {
|
||||||
|
a.actionPerformed(e);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether a character could be part of an "identifier" token in a
|
||||||
|
* specific language. This is used to identify such things as the bounds of the
|
||||||
|
* "word" to select on double-clicking.
|
||||||
|
*
|
||||||
|
* @param languageIndex
|
||||||
|
* The language index the character was found in.
|
||||||
|
* @param ch
|
||||||
|
* The character.
|
||||||
|
* @return Whether the character could be part of an "identifier" token.
|
||||||
|
*/
|
||||||
|
public boolean isIdentifierChar(int languageIndex, char ch) {
|
||||||
|
return tokenMaker.isIdentifierChar(languageIndex, ch);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an iterator over the paintable tokens in this document. Results are
|
||||||
|
* undefined if this document is modified while the iterator is being iterated
|
||||||
|
* through, so this should only be used on the EDT.
|
||||||
|
* <p>
|
||||||
|
*
|
||||||
|
* The <code>remove()</code> method of the returned iterator will throw an
|
||||||
|
* <code>UnsupportedOperationException</code>.
|
||||||
|
*
|
||||||
|
* @return An iterator.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Iterator<Token> iterator() {
|
||||||
|
return new TokenIterator(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deserializes a document.
|
||||||
|
*
|
||||||
|
* @param in
|
||||||
|
* The stream to read from.
|
||||||
|
* @throws ClassNotFoundException
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
|
private void readObject(ObjectInputStream in) throws ClassNotFoundException, IOException {
|
||||||
|
|
||||||
|
in.defaultReadObject();
|
||||||
|
|
||||||
|
// Install default TokenMakerFactory. To support custom TokenMakers,
|
||||||
|
// both JVM's should install default TokenMakerFactories that support
|
||||||
|
// the language they want to use beforehand.
|
||||||
|
setTokenMakerFactory(null);
|
||||||
|
|
||||||
|
// Handle other transient stuff
|
||||||
|
this.s = new Segment();
|
||||||
|
int lineCount = getDefaultRootElement().getElementCount();
|
||||||
|
lastTokensOnLines = new DynamicIntArray(lineCount);
|
||||||
|
setSyntaxStyle(syntaxStyle); // Actually install (transient) TokenMaker
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Makes our private <code>Segment s</code> point to the text in our document
|
||||||
|
* referenced by the specified element. Note that <code>line</code> MUST be a
|
||||||
|
* valid line number in the document.
|
||||||
|
*
|
||||||
|
* @param line
|
||||||
|
* The line number you want to get.
|
||||||
|
*/
|
||||||
|
private void setSharedSegment(int line) {
|
||||||
|
|
||||||
|
Element map = getDefaultRootElement();
|
||||||
|
// int numLines = map.getElementCount();
|
||||||
|
|
||||||
|
Element element = map.getElement(line);
|
||||||
|
if (element == null) {
|
||||||
|
throw new InternalError("Invalid line number: " + line);
|
||||||
|
}
|
||||||
|
int startOffset = element.getStartOffset();
|
||||||
|
// int endOffset = (line==numLines-1 ?
|
||||||
|
// element.getEndOffset()-1 : element.getEndOffset() - 1);
|
||||||
|
int endOffset = element.getEndOffset() - 1; // Why always "-1"?
|
||||||
|
try {
|
||||||
|
getText(startOffset, endOffset - startOffset, s);
|
||||||
|
} catch (BadLocationException ble) {
|
||||||
|
throw new InternalError("Text range not in document: " + startOffset + "-" + endOffset);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the syntax style being used for syntax highlighting in this document.
|
||||||
|
* What styles are supported by a document is determined by its
|
||||||
|
* {@link TokenMakerFactory}. By default, all <code>RSyntaxDocument</code>s
|
||||||
|
* support all languages built into <code>RSyntaxTextArea</code>.
|
||||||
|
*
|
||||||
|
* @param styleKey
|
||||||
|
* The new style to use, such as
|
||||||
|
* {@link SyntaxConstants#SYNTAX_STYLE_JAVA}. If this style is not
|
||||||
|
* known or supported by this document, then
|
||||||
|
* {@link SyntaxConstants#SYNTAX_STYLE_NONE} is used.
|
||||||
|
* @see #setSyntaxStyle(TokenMaker)
|
||||||
|
* @see #getSyntaxStyle()
|
||||||
|
*/
|
||||||
|
public void setSyntaxStyle(String styleKey) {
|
||||||
|
if (styleKey.equals("text/javaassembly")) {
|
||||||
|
tokenMaker = new JavaAssemblyTokenMaker();
|
||||||
|
} else {
|
||||||
|
tokenMaker = tokenMakerFactory.getTokenMaker(styleKey);
|
||||||
|
}
|
||||||
|
updateSyntaxHighlightingInformation();
|
||||||
|
this.syntaxStyle = styleKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the syntax style being used for syntax highlighting in this document.
|
||||||
|
* You should call this method if you've created a custom token maker for a
|
||||||
|
* language not normally supported by <code>RSyntaxTextArea</code>.
|
||||||
|
*
|
||||||
|
* @param tokenMaker
|
||||||
|
* The new token maker to use.
|
||||||
|
* @see #setSyntaxStyle(String)
|
||||||
|
*/
|
||||||
|
public void setSyntaxStyle(TokenMaker tokenMaker) {
|
||||||
|
this.tokenMaker = tokenMaker;
|
||||||
|
updateSyntaxHighlightingInformation();
|
||||||
|
this.syntaxStyle = "text/unknown"; // TODO: Make me public?
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the token maker factory used by this document.
|
||||||
|
*
|
||||||
|
* @param tmf
|
||||||
|
* The <code>TokenMakerFactory</code> for this document. If this is
|
||||||
|
* <code>null</code>, a default factory is used.
|
||||||
|
*/
|
||||||
|
public void setTokenMakerFactory(TokenMakerFactory tmf) {
|
||||||
|
tokenMakerFactory = tmf != null ? tmf : TokenMakerFactory.getDefaultInstance();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loops through the last-tokens-on-lines array from a specified point onward,
|
||||||
|
* updating last-token values until they stop changing. This should be called
|
||||||
|
* when lines are updated/inserted/removed, as doing so may cause lines below to
|
||||||
|
* change color.
|
||||||
|
*
|
||||||
|
* @param line
|
||||||
|
* The first line to check for a change in last-token value.
|
||||||
|
* @param numLines
|
||||||
|
* The number of lines in the document.
|
||||||
|
* @param previousTokenType
|
||||||
|
* The last-token value of the line just before <code>line</code>.
|
||||||
|
* @return The last line that needs repainting.
|
||||||
|
*/
|
||||||
|
private int updateLastTokensBelow(int line, int numLines, int previousTokenType) {
|
||||||
|
|
||||||
|
int firstLine = line;
|
||||||
|
|
||||||
|
// Loop through all lines past our starting point. Update even the last
|
||||||
|
// line's info, even though there aren't any lines after it that depend
|
||||||
|
// on it changing for them to be changed, as its state may be used
|
||||||
|
// elsewhere in the library.
|
||||||
|
int end = numLines;
|
||||||
|
// System.err.println("--- end==" + end + " (numLines==" + numLines + ")");
|
||||||
|
while (line < end) {
|
||||||
|
|
||||||
|
setSharedSegment(line); // Sets s's text to that of line 'line' in the document.
|
||||||
|
|
||||||
|
int oldTokenType = lastTokensOnLines.get(line);
|
||||||
|
int newTokenType = tokenMaker.getLastTokenTypeOnLine(s, previousTokenType);
|
||||||
|
// System.err.println("---------------- line " + line + "; oldTokenType==" +
|
||||||
|
// oldTokenType + ", newTokenType==" + newTokenType + ", s=='" + s + "'");
|
||||||
|
|
||||||
|
// If this line's end-token value didn't change, stop here. Note
|
||||||
|
// that we're saying this line needs repainting; this is because
|
||||||
|
// the beginning of this line did indeed change color, but the
|
||||||
|
// end didn't.
|
||||||
|
if (oldTokenType == newTokenType) {
|
||||||
|
// System.err.println("... ... ... repainting lines " + firstLine + "-" + line);
|
||||||
|
fireChangedUpdate(new DefaultDocumentEvent(firstLine, line, DocumentEvent.EventType.CHANGE));
|
||||||
|
return line;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the line's end-token value did change, update it and
|
||||||
|
// keep going.
|
||||||
|
// NOTE: "setUnsafe" is okay here as the bounds checking was
|
||||||
|
// already done in lastTokensOnLines.get(line) above.
|
||||||
|
lastTokensOnLines.setUnsafe(line, newTokenType);
|
||||||
|
previousTokenType = newTokenType;
|
||||||
|
line++;
|
||||||
|
|
||||||
|
} // End of while (line<numLines).
|
||||||
|
|
||||||
|
// If any lines had their token types changed, fire a changed update
|
||||||
|
// for them. The view will repaint the area covered by the lines.
|
||||||
|
// FIXME: We currently cheat and send the line range that needs to be
|
||||||
|
// repainted as the "offset and length" of the change, since this is
|
||||||
|
// what the view needs. We really should send the actual offset and
|
||||||
|
// length.
|
||||||
|
if (line > firstLine) {
|
||||||
|
// System.err.println("... ... ... repainting lines " + firstLine + "-" + line);
|
||||||
|
fireChangedUpdate(new DefaultDocumentEvent(firstLine, line, DocumentEvent.EventType.CHANGE));
|
||||||
|
}
|
||||||
|
|
||||||
|
return line;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates internal state information; e.g. the "last tokens on lines" data.
|
||||||
|
* After this, a changed update is fired to let listeners know that the
|
||||||
|
* document's structure has changed.
|
||||||
|
* <p>
|
||||||
|
*
|
||||||
|
* This is called internally whenever the syntax style changes.
|
||||||
|
*/
|
||||||
|
private void updateSyntaxHighlightingInformation() {
|
||||||
|
|
||||||
|
// Reinitialize the "last token on each line" array. Note that since
|
||||||
|
// the actual text in the document isn't changing, the number of lines
|
||||||
|
// is the same.
|
||||||
|
Element map = getDefaultRootElement();
|
||||||
|
int numLines = map.getElementCount();
|
||||||
|
int lastTokenType = Token.NULL;
|
||||||
|
for (int i = 0; i < numLines; i++) {
|
||||||
|
setSharedSegment(i);
|
||||||
|
lastTokenType = tokenMaker.getLastTokenTypeOnLine(s, lastTokenType);
|
||||||
|
lastTokensOnLines.set(i, lastTokenType);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear our token cache to force re-painting
|
||||||
|
lastLine = -1;
|
||||||
|
cachedTokenList = null;
|
||||||
|
|
||||||
|
// Let everybody know that syntax styles have (probably) changed.
|
||||||
|
fireChangedUpdate(new DefaultDocumentEvent(0, numLines - 1, DocumentEvent.EventType.CHANGE));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,14 @@
|
|||||||
|
ContextMenu.Folding=Folding
|
||||||
|
|
||||||
|
Action.ToggleCurrentFold.Name=Toggle Current Fold
|
||||||
|
Action.ToggleCurrentFold.Mnemonic=F
|
||||||
|
Action.ToggleCurrentFold.Desc=Toggles the fold at the caret position.
|
||||||
|
Action.CollapseAllFolds.Name=Collapse All Folds
|
||||||
|
Action.CollapseAllFolds.Mnemonic=O
|
||||||
|
Action.CollapseAllFolds.Desc=Collapses all folds.
|
||||||
|
Action.CollapseCommentFolds.Name=Collapse All Comments
|
||||||
|
Action.CollapseCommentFolds.Mnemonic=C
|
||||||
|
Action.CollapseCommentFolds.Desc=Collapses all comment folds.
|
||||||
|
Action.ExpandAllFolds.Name=Expand All Folds
|
||||||
|
Action.ExpandAllFolds.Mnemonic=E
|
||||||
|
Action.ExpandAllFolds.Desc=Expands all folds.
|
||||||
Loaded 100 of 352 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user