Skip to content

Commit 95aecb8

Browse files
committed
editor: Add tokenmarker to support custom logic
Signed-off-by: Ce Gao <ce.gao@outlook.com>
1 parent f51649c commit 95aecb8

File tree

6 files changed

+361
-6
lines changed

6 files changed

+361
-6
lines changed

resources/keywords.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ $ KEYWORD1
1313
if KEYWORD3 if
1414
print FUNCTION1
1515

16+
# comment
17+
1618
// Processing
1719

1820
abs FUNCTION1 abs_

src/rprocessing/mode/RLangEditor.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,12 @@ public void deactivateRun() {
209209
cleanupTempSketch();
210210
}
211211

212+
@Override
213+
public void handleAutoFormat() {
214+
super.handleAutoFormat();
215+
recolor();
216+
}
217+
212218
/**
213219
* @see processing.app.ui.Editor#getCommentPrefix()
214220
*/
@@ -217,6 +223,12 @@ public String getCommentPrefix() {
217223
return "# ";
218224
}
219225

226+
@Override
227+
protected void handleCommentUncomment() {
228+
super.handleCommentUncomment();
229+
recolor();
230+
}
231+
220232
/**
221233
* It is empty but cannot be removed because it is a abstract function.
222234
* @see processing.app.ui.Editor#handleImportLibrary(java.lang.String)
@@ -266,6 +278,10 @@ public void handleStop() {
266278
requestFocus();
267279
}
268280

281+
private void recolor() {
282+
textarea.getDocument().tokenizeLines();
283+
}
284+
269285
private void restoreToolbar() {
270286
toolbar.deactivateStop();
271287
toolbar.deactivateRun();
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
package rprocessing.mode;
2+
3+
import javax.swing.text.Segment;
4+
5+
import processing.app.syntax.KeywordMap;
6+
import processing.app.syntax.Token;
7+
8+
public class RLangKeywordMap extends KeywordMap {
9+
private final Keyword[] map;
10+
11+
/**
12+
* Creates a new <code>KeywordMap</code>.
13+
*/
14+
public RLangKeywordMap() {
15+
this(52);
16+
}
17+
18+
/**
19+
* Creates a new <code>KeywordMap</code>.
20+
* @param mapLength The number of `buckets' to create.
21+
* A value of 52 will give good performance for most maps.
22+
*/
23+
public RLangKeywordMap(final int mapLength) {
24+
super(true);
25+
this.mapLength = mapLength;
26+
map = new Keyword[mapLength];
27+
}
28+
29+
/**
30+
* Looks up a key.
31+
* @param text The text segment
32+
* @param offset The offset of the substring within the text segment
33+
* @param length The length of the substring
34+
*/
35+
public byte lookup(final Segment text, final int offset, final int length) {
36+
if (length == 0) {
37+
return Token.NULL;
38+
}
39+
Keyword k = map[getSegmentMapKey(text, offset, length)];
40+
while (k != null) {
41+
if (length != k.keyword.length) {
42+
k = k.next;
43+
continue;
44+
}
45+
if (SyntaxUtilities.regionMatches(text, offset, k.keyword)) {
46+
return k.id;
47+
}
48+
k = k.next;
49+
}
50+
return Token.NULL;
51+
}
52+
53+
/**
54+
* Adds a key-value mapping.
55+
* @param keyword The key
56+
* @Param id The value
57+
*/
58+
public void add(final String keyword, final byte id) {
59+
final int key = getStringMapKey(keyword);
60+
map[key] = new Keyword(keyword.toCharArray(), id, map[key]);
61+
}
62+
63+
// protected members
64+
protected int mapLength;
65+
66+
@Override
67+
protected int getStringMapKey(final String s) {
68+
return (Character.toUpperCase(s.charAt(0))
69+
+ Character.toUpperCase(s.charAt(s.length() - 1)))
70+
% mapLength;
71+
}
72+
73+
@Override
74+
protected int getSegmentMapKey(final Segment s, final int off, final int len) {
75+
return (Character.toUpperCase(s.array[off]) + Character.toUpperCase(s.array[off + len - 1]))
76+
% mapLength;
77+
}
78+
79+
// private members
80+
class Keyword {
81+
public Keyword(final char[] keyword, final byte id, final Keyword next) {
82+
this.keyword = keyword;
83+
this.id = id;
84+
this.next = next;
85+
}
86+
87+
public char[] keyword;
88+
public byte id;
89+
public Keyword next;
90+
}
91+
92+
}

src/rprocessing/mode/RLangMode.java

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import processing.app.Base;
66
import processing.app.Messages;
77
import processing.app.Mode;
8+
import processing.app.syntax.TokenMarker;
89
import processing.app.ui.Editor;
910
import processing.app.ui.EditorException;
1011
import processing.app.ui.EditorState;
@@ -17,16 +18,16 @@
1718
*/
1819
public class RLangMode extends Mode {
1920

20-
private static final boolean VERBOSE = Boolean.parseBoolean(System
21-
.getenv("VERBOSE_RLANG_MODE"));
21+
private static final boolean VERBOSE = Boolean
22+
.parseBoolean(System.getenv("VERBOSE_RLANG_MODE"));
2223

2324
/**
2425
* If the environment variable SKETCH_RUNNER_FIRST is equal to the string "true", then
2526
* {@link RLangMode} expects that the {@link SketchRunner} is already running and waiting
2627
* to be communicated with (as when you're debugging it in Eclipse, for example).
2728
*/
28-
public static final boolean SKETCH_RUNNER_FIRST = Boolean.parseBoolean(System
29-
.getenv("SKETCH_RUNNER_FIRST"));
29+
public static final boolean SKETCH_RUNNER_FIRST = Boolean
30+
.parseBoolean(System.getenv("SKETCH_RUNNER_FIRST"));
3031

3132
private final SketchServiceManager sketchServiceManager;
3233

@@ -51,8 +52,8 @@ public SketchServiceManager getSketchServiceManager() {
5152
* @see processing.app.Mode#createEditor(processing.app.Base, java.lang.String, processing.app.ui.EditorState)
5253
*/
5354
@Override
54-
public Editor createEditor(Base base, final String path, final EditorState state)
55-
throws EditorException {
55+
public Editor createEditor(Base base, final String path,
56+
final EditorState state) throws EditorException {
5657
// Lazily start the sketch running service only when an editor is required.
5758
if (!sketchServiceManager.isStarted()) {
5859
sketchServiceManager.start();
@@ -81,6 +82,11 @@ public String getModuleExtension() {
8182
return "R";
8283
}
8384

85+
@Override
86+
public TokenMarker createTokenMarker() {
87+
return new RLangTokenMarker();
88+
}
89+
8490
/**
8591
* @see processing.app.Mode#getExtensions()
8692
*/
Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
package rprocessing.mode;
2+
3+
import javax.swing.text.Segment;
4+
5+
import processing.app.syntax.Token;
6+
import processing.app.syntax.TokenMarker;
7+
8+
public class RLangTokenMarker extends TokenMarker {
9+
10+
private static final byte TRIPLEQUOTE1 = Token.INTERNAL_FIRST;
11+
private static final byte TRIPLEQUOTE2 = Token.INTERNAL_LAST;
12+
13+
private static RLangKeywordMap rLangKeywordMap;
14+
15+
private final RLangKeywordMap keywords;
16+
private int lastOffset;
17+
private int lastKeyword;
18+
19+
public RLangTokenMarker() {
20+
this.keywords = getKeywords();
21+
}
22+
23+
@Override
24+
public void addColoring(final String keyword, final String coloring) {
25+
// KEYWORD1 -> 0, KEYWORD2 -> 1, etc
26+
final int num = coloring.charAt(coloring.length() - 1) - '1';
27+
int id = 0;
28+
boolean paren = false;
29+
switch (coloring.charAt(0)) {
30+
case 'K':
31+
id = Token.KEYWORD1 + num;
32+
break;
33+
case 'L':
34+
id = Token.LITERAL1 + num;
35+
break;
36+
case 'F':
37+
id = Token.FUNCTION1 + num;
38+
paren = true;
39+
break;
40+
}
41+
keywords.add(keyword, (byte) id);
42+
}
43+
44+
@Override
45+
public byte markTokensImpl(byte token, final Segment line, final int lineIndex) {
46+
final char[] array = line.array;
47+
final int offset = line.offset;
48+
lastOffset = offset;
49+
lastKeyword = offset;
50+
final int length = line.count + offset;
51+
boolean backslash = false;
52+
53+
loop: for (int i = offset; i < length; i++) {
54+
final int i1 = (i + 1);
55+
56+
final char c = array[i];
57+
if (c == '\\') {
58+
backslash = !backslash;
59+
continue;
60+
}
61+
62+
switch (token) {
63+
case Token.NULL:
64+
switch (c) {
65+
case '#':
66+
if (backslash) {
67+
backslash = false;
68+
} else {
69+
doKeyword(line, i, c);
70+
addToken(i - lastOffset, token);
71+
addToken(length - i, Token.COMMENT1);
72+
lastOffset = lastKeyword = length;
73+
break loop;
74+
}
75+
break;
76+
case '"':
77+
doKeyword(line, i, c);
78+
if (backslash) {
79+
backslash = false;
80+
} else {
81+
addToken(i - lastOffset, token);
82+
if (SyntaxUtilities.regionMatches(line, i1, "\"\"")) {
83+
token = TRIPLEQUOTE1;
84+
} else {
85+
token = Token.LITERAL1;
86+
}
87+
lastOffset = lastKeyword = i;
88+
}
89+
break;
90+
case '\'':
91+
doKeyword(line, i, c);
92+
if (backslash) {
93+
backslash = false;
94+
} else {
95+
addToken(i - lastOffset, token);
96+
if (SyntaxUtilities.regionMatches(line, i1, "''")) {
97+
token = TRIPLEQUOTE2;
98+
} else {
99+
token = Token.LITERAL2;
100+
}
101+
lastOffset = lastKeyword = i;
102+
}
103+
break;
104+
default:
105+
backslash = false;
106+
if (!Character.isLetterOrDigit(c) && c != '_') {
107+
doKeyword(line, i, c);
108+
}
109+
break;
110+
}
111+
break;
112+
case Token.LITERAL1:
113+
if (backslash) {
114+
backslash = false;
115+
} else if (c == '"') {
116+
addToken(i1 - lastOffset, token);
117+
token = Token.NULL;
118+
lastOffset = lastKeyword = i1;
119+
}
120+
break;
121+
case Token.LITERAL2:
122+
if (backslash) {
123+
backslash = false;
124+
} else if (c == '\'') {
125+
addToken(i1 - lastOffset, Token.LITERAL1);
126+
token = Token.NULL;
127+
lastOffset = lastKeyword = i1;
128+
}
129+
break;
130+
case TRIPLEQUOTE1:
131+
if (backslash) {
132+
backslash = false;
133+
} else if (SyntaxUtilities.regionMatches(line, i, "\"\"\"")) {
134+
addToken((i += 3) - lastOffset, Token.LITERAL1);
135+
token = Token.NULL;
136+
lastOffset = lastKeyword = i;
137+
}
138+
break;
139+
case TRIPLEQUOTE2:
140+
if (backslash) {
141+
backslash = false;
142+
} else if (SyntaxUtilities.regionMatches(line, i, "'''")) {
143+
addToken((i += 3) - lastOffset, Token.LITERAL1);
144+
token = Token.NULL;
145+
lastOffset = lastKeyword = i;
146+
}
147+
break;
148+
default:
149+
throw new InternalError("Invalid state: " + token);
150+
}
151+
}
152+
153+
switch (token) {
154+
case TRIPLEQUOTE1:
155+
case TRIPLEQUOTE2:
156+
addToken(length - lastOffset, Token.LITERAL1);
157+
break;
158+
case Token.NULL:
159+
doKeyword(line, length, '\0');
160+
//$FALL-THROUGH$
161+
default:
162+
addToken(length - lastOffset, token);
163+
break;
164+
}
165+
166+
return token;
167+
}
168+
169+
public static RLangKeywordMap getKeywords() {
170+
if (rLangKeywordMap == null) {
171+
rLangKeywordMap = new RLangKeywordMap();
172+
173+
}
174+
return rLangKeywordMap;
175+
}
176+
177+
private boolean doKeyword(final Segment line, final int i, final char c) {
178+
final int i1 = i + 1;
179+
180+
final int len = i - lastKeyword;
181+
final byte id = keywords.lookup(line, lastKeyword, len);
182+
if (id != Token.NULL) {
183+
if (lastKeyword != lastOffset) {
184+
addToken(lastKeyword - lastOffset, Token.NULL);
185+
}
186+
addToken(len, id);
187+
lastOffset = i;
188+
}
189+
lastKeyword = i1;
190+
return false;
191+
}
192+
}

0 commit comments

Comments
 (0)