Skip to content

Commit f579467

Browse files
author
iTob
committed
Add support for punctuation in generated textmate grammars [#3337]
Signed-off-by: iTob <itobdev@gmail.com>
1 parent e0f3193 commit f579467

File tree

1 file changed

+52
-11
lines changed
  • org.eclipse.xtext.xtext.generator/src/org/eclipse/xtext/xtext/generator/textmate

1 file changed

+52
-11
lines changed

org.eclipse.xtext.xtext.generator/src/org/eclipse/xtext/xtext/generator/textmate/TextMateGrammar.java

+52-11
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,17 @@
1616
import java.util.Map;
1717
import java.util.Set;
1818
import java.util.TreeMap;
19+
import java.util.function.Predicate;
20+
import java.util.regex.Matcher;
21+
import java.util.regex.Pattern;
1922
import java.util.stream.Collectors;
2023

2124
import org.eclipse.xtext.Grammar;
2225
import org.eclipse.xtext.GrammarUtil;
2326
import org.eclipse.xtext.TerminalRule;
2427

2528
import com.google.common.base.Joiner;
29+
import com.google.common.collect.Iterables;
2630
import com.google.gson.annotations.Expose;
2731

2832
/**
@@ -35,6 +39,8 @@
3539
*/
3640
public class TextMateGrammar {
3741

42+
private static final String ANY_OTHER = "ANY_OTHER";
43+
3844
@Expose private final List<TextMateRule> patterns;
3945
@Expose private String scopeName;
4046
@Expose private Map<String, TextMateRule> repository;
@@ -125,8 +131,7 @@ protected TextMateGrammar init(Grammar grammar) {
125131
}
126132
TextMateGrammar result = new TextMateGrammar();
127133
result.setScopeName(scopeName);
128-
TextMateRule keywords = getKeywordControlRule(grammar, ignoreCase);
129-
result.addRule(keywords);
134+
result.addRule(getKeywordControlRule(grammar, ignoreCase));
130135

131136
Set<String> seenTerminalRules = new HashSet<>();
132137
for(TextMateRule pattern: patterns) {
@@ -143,7 +148,7 @@ protected TextMateGrammar init(Grammar grammar) {
143148
if (inferPatterns) {
144149
List<TerminalRule> terminals = GrammarUtil.allTerminalRules(grammar)
145150
.stream()
146-
.filter(r -> !r.isFragment())
151+
.filter(r -> !r.isFragment() && !r.getName().equals(ANY_OTHER))
147152
.collect(Collectors.toList());
148153
for(TerminalRule terminal: terminals) {
149154
if (!seenTerminalRules.add(terminal.getName())) {
@@ -154,6 +159,14 @@ protected TextMateGrammar init(Grammar grammar) {
154159
auto.init(grammar, ignoreCase, generator).ifPresent(result::addRule);
155160
}
156161
}
162+
163+
result.addRule(getPunctuationRule(grammar, ignoreCase));
164+
// invalid rule must be last, otherwise it prevents other rules from matching
165+
if (inferPatterns && GrammarUtil.findRuleForName(grammar, ANY_OTHER) != null) {
166+
AutoRule auto = newAutoRule();
167+
auto.setTerminalRule(ANY_OTHER);
168+
auto.init(grammar, ignoreCase, generator).ifPresent(result::addRule);
169+
}
157170
return result;
158171
}
159172

@@ -164,24 +177,52 @@ protected AutoRule newAutoRule() {
164177
protected String getLanguageName(Grammar grammar) {
165178
return GrammarUtil.getSimpleName(grammar).toLowerCase(Locale.ROOT);
166179
}
167-
180+
168181
protected TextMateRule getKeywordControlRule(Grammar grammar, boolean ignoreCase) {
182+
return createKeywordRule(grammar, "keyword.control", keyword -> keyword.matches("\\w+"), ignoreCase);
183+
}
184+
185+
protected TextMateRule getPunctuationRule(Grammar grammar, boolean ignoreCase) {
186+
return createKeywordRule(grammar, "punctuation", keyword -> !keyword.matches("\\w+"), ignoreCase);
187+
}
188+
189+
protected TextMateRule createKeywordRule(Grammar grammar, String namePrefix, Predicate<String> filter, boolean ignoreCase) {
169190
StringBuilder matchBuilder = new StringBuilder();
170191
if (ignoreCase) {
171192
matchBuilder.append("(?i)");
172193
}
173-
matchBuilder.append("\\b(");
194+
matchBuilder.append("(");
174195
List<String> allKeywords = GrammarUtil.getAllKeywords(grammar)
175196
.stream()
176-
.filter(s->s.matches("\\w+"))
177-
.sorted(Comparator.naturalOrder())
178-
.collect(Collectors.toList());
179-
matchBuilder.append(Joiner.on("|").join(allKeywords));
180-
matchBuilder.append(")\\b");
197+
.filter(filter)
198+
.sorted()
199+
.toList();
200+
Joiner.on("|").appendTo(matchBuilder, Iterables.transform(allKeywords, this::escapeAndAddWordBoundaries));
201+
matchBuilder.append(")");
181202
MatchRule result = new MatchRule();
182-
result.setName("keyword.control." + getLanguageName(grammar));
203+
result.setName(namePrefix + "." + getLanguageName(grammar));
183204
result.setMatch(matchBuilder.toString());
184205
return result;
185206
}
186207

208+
private static final Pattern START_IS_LETTER = Pattern.compile("^\\w");
209+
private static final Pattern END_IS_LETTER = Pattern.compile("\\w$");
210+
protected String escapeAndAddWordBoundaries(String token) {
211+
StringBuilder result = new StringBuilder();
212+
if (START_IS_LETTER.matcher(token).find()) {
213+
result.append("\\b");
214+
}
215+
result.append(escapeForRegex(token));
216+
if (END_IS_LETTER.matcher(token).find()) {
217+
result.append("\\b");
218+
}
219+
return result.toString();
220+
}
221+
222+
private static final Pattern REGEX_CONTROL_CHARS = Pattern.compile("[\\\\^$.*+?()\\[\\]{}|]");
223+
private static String escapeForRegex(String input) {
224+
Matcher matcher = REGEX_CONTROL_CHARS.matcher(input);
225+
return matcher.replaceAll(match -> Matcher.quoteReplacement("\\" + match.group()));
226+
}
227+
187228
}

0 commit comments

Comments
 (0)