2 * Copyright 2000-2010 JetBrains s.r.o.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
16 package com
.intellij
.codeInsight
.template
;
18 import com
.intellij
.codeInsight
.template
.impl
.TemplateImpl
;
19 import com
.intellij
.ide
.highlighter
.HtmlFileType
;
20 import com
.intellij
.openapi
.diagnostic
.Logger
;
21 import com
.intellij
.openapi
.editor
.Editor
;
22 import com
.intellij
.openapi
.editor
.ScrollType
;
23 import com
.intellij
.psi
.xml
.XmlFile
;
24 import com
.intellij
.util
.containers
.HashMap
;
25 import org
.jetbrains
.annotations
.NotNull
;
26 import org
.jetbrains
.annotations
.Nullable
;
28 import java
.util
.ArrayList
;
29 import java
.util
.Collection
;
30 import java
.util
.List
;
34 * @author Eugene.Kudelevsky
36 public class XmlCustomLiveTemplate
implements CustomLiveTemplate
{
37 private static final Logger LOG
= Logger
.getInstance("#com.intellij.codeInsight.template.XmlCustomLiveTemplate");
39 private static final String ATTRS
= "ATTRS";
41 private static final String POSSIBLE_OPERATIONS
= ">+*";
42 private static final String HTML_SELECTORS
= ".#";
43 private static final char MARKER
= '$';
45 private static enum MyState
{
46 OPERATION
, WORD
, AFTER_NUMBER
, NUMBER
49 private static class MyToken
{
52 private static class MyMarkerToken
extends MyToken
{
55 private static class MyTemplateToken
extends MyToken
{
58 MyTemplateToken(String key
) {
63 private static class MyNumberToken
extends MyToken
{
66 MyNumberToken(int number
) {
71 private static class MyOperationToken
extends MyToken
{
74 MyOperationToken(char sign
) {
79 private static boolean isTemplateKeyPart(char c
) {
80 return !Character
.isWhitespace(c
) && POSSIBLE_OPERATIONS
.indexOf(c
) < 0;
83 private static int parseNonNegativeInt(@NotNull String s
) {
85 return Integer
.parseInt(s
);
87 catch (Throwable ignored
) {
92 private static String
getPrefix(@NotNull String templateKey
) {
93 for (int i
= 0, n
= templateKey
.length(); i
< n
; i
++) {
94 char c
= templateKey
.charAt(i
);
95 if (HTML_SELECTORS
.indexOf(c
) >= 0) {
96 return templateKey
.substring(0, i
);
103 private static List
<MyToken
> parse(@NotNull String text
, @NotNull CustomTemplateCallback callback
) {
105 StringBuilder templateKeyBuilder
= new StringBuilder();
106 List
<MyToken
> result
= new ArrayList
<MyToken
>();
107 for (int i
= 0, n
= text
.length(); i
< n
; i
++) {
108 char c
= text
.charAt(i
);
109 if (i
== n
- 1 || POSSIBLE_OPERATIONS
.indexOf(c
) >= 0) {
110 String key
= templateKeyBuilder
.toString();
111 templateKeyBuilder
= new StringBuilder();
112 int num
= parseNonNegativeInt(key
);
114 result
.add(new MyNumberToken(num
));
117 if (key
.length() == 0) {
120 String prefix
= getPrefix(key
);
121 if (callback
.isLiveTemplateApplicable(prefix
)) {
122 if (!prefix
.equals(key
) && !callback
.isTemplateContainsVars(prefix
, ATTRS
)) {
126 else if (prefix
.indexOf('<') >= 0) {
129 result
.add(new MyTemplateToken(key
));
131 result
.add(i
< n
- 1 ?
new MyOperationToken(c
) : new MyMarkerToken());
133 else if (isTemplateKeyPart(c
)) {
134 templateKeyBuilder
.append(c
);
143 private static boolean check(@NotNull Collection
<MyToken
> tokens
) {
144 MyState state
= MyState
.WORD
;
145 for (MyToken token
: tokens
) {
146 if (token
instanceof MyMarkerToken
) {
151 if (token
instanceof MyOperationToken
) {
152 state
= ((MyOperationToken
)token
).mySign
== '*' ? MyState
.NUMBER
: MyState
.WORD
;
159 if (token
instanceof MyTemplateToken
) {
160 state
= MyState
.OPERATION
;
167 if (token
instanceof MyNumberToken
) {
168 state
= MyState
.AFTER_NUMBER
;
175 if (token
instanceof MyOperationToken
&& ((MyOperationToken
)token
).mySign
!= '*') {
176 state
= MyState
.WORD
;
184 return state
== MyState
.OPERATION
|| state
== MyState
.AFTER_NUMBER
;
187 public boolean isApplicable(@NotNull String key
, @NotNull CustomTemplateCallback callback
) {
188 if (callback
.getFile() instanceof XmlFile
) {
189 List
<MyToken
> tokens
= parse(key
, callback
);
190 if (tokens
!= null) {
191 return check(tokens
);
197 public void execute(@NotNull String key
, @NotNull CustomTemplateCallback callback
, @Nullable TemplateInvokationListener listener
) {
198 List
<MyToken
> tokens
= parse(key
, callback
);
199 assert tokens
!= null;
200 MyInterpreter interpreter
= new MyInterpreter(tokens
, callback
, MyState
.WORD
, listener
);
201 interpreter
.invoke(0);
204 private static void fail() {
205 LOG
.error("Input string was checked incorrectly during isApplicable() invokation");
209 private static String
buildAttributesString(@Nullable String id
, @NotNull List
<String
> classes
) {
210 StringBuilder result
= new StringBuilder();
212 result
.append("id=\"").append(id
).append('"');
213 if (classes
.size() > 0) {
217 if (classes
.size() > 0) {
218 result
.append("class=\"");
219 for (int i
= 0; i
< classes
.size(); i
++) {
220 result
.append(classes
.get(i
));
221 if (i
< classes
.size() - 1) {
227 return result
.toString();
230 private static boolean invokeTemplate(String key
, final CustomTemplateCallback callback
, final TemplateInvokationListener listener
) {
231 if (callback
.getFile().getFileType() instanceof HtmlFileType
) {
232 String templateKey
= null;
234 final List
<String
> classes
= new ArrayList
<String
>();
235 StringBuilder builder
= new StringBuilder();
238 for (int i
= 0, n
= key
.length(); i
< n
; i
++) {
239 char c
= key
.charAt(i
);
240 if (c
== '#' || c
== '.' || i
== n
- 1) {
243 templateKey
= builder
.toString();
246 id
= builder
.toString();
249 if (builder
.length() > 0) {
250 classes
.add(builder
.toString());
255 builder
= new StringBuilder();
261 String attributes
= buildAttributesString(id
, classes
);
262 return startTemplate(templateKey
, callback
, listener
, attributes
.length() > 0 ?
' ' + attributes
: null);
264 return startTemplate(key
, callback
, listener
, null);
267 private static boolean startTemplate(String key
,
268 CustomTemplateCallback callback
,
269 TemplateInvokationListener listener
,
270 @Nullable String attributes
) {
271 Map
<String
, String
> predefinedValues
= null;
272 if (attributes
!= null) {
273 predefinedValues
= new HashMap
<String
, String
>();
274 predefinedValues
.put(ATTRS
, attributes
);
276 if (callback
.isLiveTemplateApplicable(key
)) {
277 return callback
.startTemplate(key
, predefinedValues
, listener
);
280 TemplateImpl template
= new TemplateImpl("", "");
281 template
.addTextSegment('<' + key
);
282 if (attributes
!= null) {
283 template
.addVariable(ATTRS
, "", "", false);
284 template
.addVariableSegment(ATTRS
);
286 template
.addTextSegment(">");
287 template
.addVariableSegment(TemplateImpl
.END
);
288 template
.addTextSegment("</" + key
+ ">");
289 template
.setToReformat(true);
290 return callback
.startTemplate(template
, predefinedValues
, listener
);
294 private class MyInterpreter
{
295 private final List
<MyToken
> myTokens
;
296 private final CustomTemplateCallback myCallback
;
297 private final TemplateInvokationListener myListener
;
298 private MyState myState
;
299 private int myEndOffset
= -1;
301 private MyInterpreter(List
<MyToken
> tokens
,
302 CustomTemplateCallback callback
,
303 MyState initialState
,
304 TemplateInvokationListener listener
) {
306 myCallback
= callback
;
307 myListener
= listener
;
308 myState
= initialState
;
311 private void fixEndOffset() {
312 if (myEndOffset
< 0) {
313 myEndOffset
= getOffset();
317 private int getOffset() {
318 return myCallback
.getEditor().getCaretModel().getOffset();
321 private void finish(boolean inSeparateEvent
) {
322 Editor editor
= myCallback
.getEditor();
323 if (myEndOffset
>= 0) {
324 editor
.getCaretModel().moveToOffset(myEndOffset
);
326 editor
.getScrollingModel().scrollToCaret(ScrollType
.MAKE_VISIBLE
);
327 if (myListener
!= null) {
328 myListener
.finished(inSeparateEvent
);
332 public boolean invoke(int startIndex
) {
333 final int n
= myTokens
.size();
334 String templateKey
= null;
336 for (int i
= startIndex
; i
< n
; i
++) {
337 final int finalI
= i
;
338 MyToken token
= myTokens
.get(i
);
341 if (templateKey
!= null) {
342 if (token
instanceof MyMarkerToken
|| token
instanceof MyOperationToken
) {
343 final char sign
= token
instanceof MyOperationToken ?
((MyOperationToken
)token
).mySign
: MARKER
;
344 if (sign
== MARKER
|| sign
== '+') {
345 final Object key
= new Object();
346 myCallback
.fixStartOfTemplate(key
);
347 TemplateInvokationListener listener
= new TemplateInvokationListener() {
348 public void finished(boolean inSeparateEvent
) {
349 myState
= MyState
.WORD
;
352 myCallback
.gotoEndOfTemplate(key
);
354 if (inSeparateEvent
) {
359 if (!invokeTemplate(templateKey
, myCallback
, listener
)) {
364 else if (sign
== '>') {
365 if (!startTemplate(templateKey
, finalI
)) {
370 else if (sign
== '*') {
371 myState
= MyState
.NUMBER
;
380 if (token
instanceof MyTemplateToken
) {
381 templateKey
= ((MyTemplateToken
)token
).myKey
;
382 myState
= MyState
.OPERATION
;
389 if (token
instanceof MyNumberToken
) {
390 number
= ((MyNumberToken
)token
).myNumber
;
391 myState
= MyState
.AFTER_NUMBER
;
398 if (token
instanceof MyMarkerToken
|| token
instanceof MyOperationToken
) {
399 char sign
= token
instanceof MyOperationToken ?
((MyOperationToken
)token
).mySign
: MARKER
;
400 if (sign
== MARKER
|| sign
== '+') {
401 if (!invokeTemplateSeveralTimes(templateKey
, number
, finalI
)) {
406 else if (number
> 1) {
407 return invokeTemplateAndProcessTail(templateKey
, i
+ 1, number
);
411 if (!startTemplate(templateKey
, finalI
)) {
416 myState
= MyState
.WORD
;
424 finish(startIndex
== n
);
428 private boolean startTemplate(String templateKey
, final int index
) {
429 TemplateInvokationListener listener
= new TemplateInvokationListener() {
430 public void finished(boolean inSeparateEvent
) {
431 myState
= MyState
.WORD
;
432 if (inSeparateEvent
) {
437 if (!invokeTemplate(templateKey
, myCallback
, listener
)) {
443 private boolean invokeTemplateSeveralTimes(final String templateKey
, final int count
, final int index
) {
444 final Object key
= new Object();
445 myCallback
.fixStartOfTemplate(key
);
446 for (int i
= 0; i
< count
; i
++) {
447 final int finalI
= i
;
448 TemplateInvokationListener listener
= new TemplateInvokationListener() {
449 public void finished(boolean inSeparateEvent
) {
450 myState
= MyState
.WORD
;
452 myCallback
.gotoEndOfTemplate(key
);
453 if (inSeparateEvent
) {
454 int newCount
= count
- finalI
- 1;
456 invokeTemplateSeveralTimes(templateKey
, newCount
, index
);
464 if (!invokeTemplate(templateKey
, myCallback
, listener
)) {
471 private boolean invokeTemplateAndProcessTail(final String templateKey
, final int tailStart
, final int count
) {
472 final Object key
= new Object();
473 myCallback
.fixStartOfTemplate(key
);
474 for (int i
= 0; i
< count
; i
++) {
475 final boolean[] flag
= new boolean[]{false};
476 final int finalI
= i
;
477 TemplateInvokationListener listener
= new TemplateInvokationListener() {
478 public void finished(boolean inSeparateEvent
) {
479 MyInterpreter interpreter
= new MyInterpreter(myTokens
, myCallback
, MyState
.WORD
, new TemplateInvokationListener() {
480 public void finished(boolean inSeparateEvent
) {
482 myCallback
.gotoEndOfTemplate(key
);
483 if (inSeparateEvent
) {
484 invokeTemplateAndProcessTail(templateKey
, tailStart
, count
- finalI
- 1);
488 if (interpreter
.invoke(tailStart
)) {
489 if (inSeparateEvent
) {
490 invokeTemplateAndProcessTail(templateKey
, tailStart
, count
- finalI
- 1);
498 if (!invokeTemplate(templateKey
, myCallback
, listener
) || flag
[0]) {