PageRenderTime 84ms CodeModel.GetById 56ms app.highlight 23ms RepoModel.GetById 1ms app.codeStats 0ms

/bundles/plugins-trunk/RubyPlugin/src/org/jedit/ruby/structure/AutoIndentAndInsertEnd.java

#
Java | 553 lines | 389 code | 69 blank | 95 comment | 116 complexity | c33e1e38b10de597532cc753f7fdf129 MD5 | raw file
  1/*
  2 * AutoIndentAndInsertEnd.java - auto indents and inserts end keyword
  3 *
  4 * Copyright 2005 Robert McKinnon
  5 *
  6 * This program is free software; you can redistribute it and/or
  7 * modify it under the terms of the GNU General Public License
  8 * as published by the Free Software Foundation; either version 2
  9 * of the License, or any later version.
 10 *
 11 * This program is distributed in the hope that it will be useful,
 12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 14 * GNU General Public License for more details.
 15 *
 16 * You should have received a copy of the GNU General Public License
 17 * along with this program; if not, write to the Free Software
 18 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 19 */
 20package org.jedit.ruby.structure;
 21
 22import org.gjt.sp.jedit.View;
 23import org.gjt.sp.jedit.Buffer;
 24import org.gjt.sp.jedit.textarea.JEditTextArea;
 25import org.jedit.ruby.RubyPlugin;
 26import org.jedit.ruby.utils.RegularExpression;
 27
 28import java.util.regex.MatchResult;
 29
 30/**
 31 * <p>This action is intended to be executed each time the enter key is pressed.
 32 * Add the 'ENTER' key shortcut via:<ul><li>
 33 * Utilities -> Global Options -> Shortcuts -> Edit Shortcuts: RubyPlugin
 34 * </li></ul>
 35 * </p>
 36 * <p/>
 37 * Currently auto inserts and indents <code>end</code> after the following patterns:<pre>
 38 *   if x
 39 *   for x
 40 *   while x
 41 *   until x
 42 *   unless x
 43 *   def x
 44 *   case x
 45 *   class x
 46 *   module x
 47 *   begin
 48 *   loop do
 49 *   y do |z|
 50 * </pre>
 51 * It also:<ul>
 52 * <li>auto-aligns <code>else</code> and <code>end</code> keywords</li>
 53 * <li>removes empty brackets () when you press enter inside of them</li>
 54 * </ul>
 55 * </p>
 56 *
 57 * @author robmckinnon at users.sourceforge.net
 58 */
 59public final class AutoIndentAndInsertEnd {
 60
 61    private static final AutoIndentAndInsertEnd instance = new AutoIndentAndInsertEnd();
 62
 63    private JEditTextArea area;
 64
 65    /**
 66     * Singleton private constructor
 67     */
 68    private AutoIndentAndInsertEnd() {
 69    }
 70
 71    public static void performIndent(View view) {
 72        instance.innerPerformIndent(view);
 73    }
 74
 75    private void innerPerformIndent(View view) {
 76        area = view.getTextArea();
 77        Buffer buffer = view.getBuffer();
 78        buffer.writeLock();
 79        buffer.beginCompoundEdit();
 80        try {
 81            doIndent();
 82        } finally {
 83            if (buffer.insideCompoundEdit()) {
 84                buffer.endCompoundEdit();
 85            }
 86            buffer.writeUnlock();
 87        }
 88    }
 89
 90    private void doIndent() {
 91        area.removeTrailingWhiteSpace();
 92        int row = area.getCaretLine();
 93        String line = area.getLineText(row);
 94        String trimLine = line.trim();
 95        int caretPosition = area.getCaretPosition() - area.getLineStartOffset(row);
 96        boolean openingBrace = line.indexOf("{") != -1 && line.indexOf("}") == -1;
 97
 98        if (caretPosition != line.length() || openingBrace) {
 99            if (CommentRegExp.instance.isMatch(line)) {
100                handleComment(line, row);
101            } else {
102                boolean oneCharFromEnd = caretPosition == line.length() - 1;
103                boolean insideEmptyParenthesis = oneCharFromEnd && caretPosition != 0 && line.charAt(caretPosition - 1) == '(' && line.charAt(caretPosition) == ')';
104                if (insideEmptyParenthesis) {
105                    area.selectLine();
106                    area.setSelectedText(line.substring(0, line.length() - 2));
107                }
108                area.insertEnterAndIndent();
109            }
110        } else if (trimLine.startsWith("else") || trimLine.startsWith("elsif")) {
111            handleElse(trimLine, row);
112        } else {
113//            String text = area.getText();
114//            int offset = area.getCaretPosition();
115//            if (text.indexOf('\n', offset) == text.lastIndexOf('\n', offset)) {
116//                area.setText(text + '\n');
117//            }
118            handleInsertEnter(trimLine, row, line);
119        }
120    }
121
122    private void handleInsertEnter(String trimLine, int row, String line) {
123        boolean matchesConditionalAssignment = TrailingConditionRegExp.instance.isMatch(line);
124        boolean matchesDo = DoRegExp.instance.isMatch(line) && !isDoInComment(line);
125        boolean matchesSyntax = MatchRegExp.instance.isMatch(line);
126        boolean ignore = IgnoreRegExp.instance.isMatch(line);
127
128        if (matchesConditionalAssignment) {
129            handleInsertEnd(TrailingConditionRegExp.instance, line);
130
131        } else if (!ignore && matchesDo) {
132            handleInsertEnd(DoRegExp.instance, line);
133
134        } else if (!ignore && matchesSyntax) {
135            handleInsertEnd(MatchRegExp.instance, line);
136
137        } else if (EndRegExp.instance.isMatch(trimLine)) {
138            handleEnd(trimLine, row);
139        } else if (CommentRegExp.instance.isMatch(trimLine)) {
140            handleComment(line, row);
141        } else {
142            area.insertEnterAndIndent();
143            while(line.trim().length() == 0 && row > 0) {
144                row--;
145                line = area.getLineText(row);
146            }
147            line = line.trim();
148            if(line.endsWith("end")
149                    && line.substring(0, line.length()-3).trim().endsWith(";")
150                    && line.indexOf("#") == -1) {
151                area.shiftIndentLeft();
152            }
153        }
154    }
155
156    private void handleElse(String trimLine, int row) {
157        area.insertEnterAndIndent();
158
159        if (row > 0) {
160            int index = row;
161
162            while (index > 0) {
163                index--;
164                String line = area.getLineText(index);
165                if (TrailingConditionRegExp.instance.isMatch(line) && line.indexOf("elsif") == -1) {
166                    indentAfterTrailingConditionalAssignment(line, trimLine);
167                    break;
168
169                } else if(IfElsifRegExp.instance.isMatch(line)) {
170                    MatchResult matches = IfElsifRegExp.instance.firstMatch(line);
171                    String indent = matches.group(1);
172                    reIndent(trimLine, indent);
173                    area.selectLine();
174                    area.setSelectedText(indent + area.getSelectedText().trim());
175                    area.shiftIndentRight();
176                    break;
177                }
178            }
179        }
180    }
181
182    private void indentAfterTrailingConditionalAssignment(String line, String trimLine) {
183        String indent = TrailingConditionRegExp.instance.indent(line);
184        reIndent(trimLine, indent);
185        area.selectLine();
186        area.setSelectedText(indent + area.getSelectedText().trim());
187        area.shiftIndentRight();
188
189        MatchResult matches = TrailingConditionRegExp.instance.firstMatch(line);
190        if (matches.group(3).startsWith("case")) {
191            area.goToPrevLine(false);
192            area.shiftIndentRight();
193            area.goToNextLine(false);
194            area.shiftIndentRight();
195        }
196    }
197
198    private void reIndent(String trimLine, String indent) {
199        area.goToPrevLine(false);
200        area.selectLine();
201        area.setSelectedText(indent + trimLine);
202        area.goToNextLine(false);
203    }
204
205    private void handleComment(String line, int row) {
206        area.insertEnterAndIndent();
207        if (row > 0) {
208            int index = row;
209            while (index > 0) {
210                line = area.getLineText(index);
211                index--;
212
213                if (CommentRegExp.instance.isMatch(line)) {
214                    MatchResult matches = CommentRegExp.instance.firstMatch(line);
215                    String hashes = matches.group(2);
216                    if (hashes.equals("##")) {
217                        String indent = matches.group(1);
218                        area.selectLine();
219                        String text = area.getSelectedText() == null ? "" : area.getSelectedText();
220                        text = text.trim();
221                        area.setSelectedText(indent + "# " + text);
222                        area.goToEndOfLine(false);
223                        break;
224                    }
225                } else {
226                    break;
227                }
228            }
229        }
230    }
231
232    private void handleEnd(String trimLine, int row) {
233        area.insertEnterAndIndent();
234        if (row > 0) {
235            int index = row;
236            int endCount = 0;
237            while (index > 0) {
238                index--;
239                String line = area.getLineText(index);
240
241                if (EndRegExp.instance.isMatch(line)) {
242                    endCount++;
243
244                } else if (!IgnoreRegExp.instance.isMatch(line) || TrailingConditionRegExp.instance.isMatch(line)) {
245                    boolean notElse = line.indexOf("elsif") == -1 && line.indexOf("else") == -1;
246
247                    boolean isDoStatement = DoRegExp.instance.isMatch(line) && !isDoInComment(line);
248                    boolean isSyntaxStatement = MatchRegExp.instance.isMatch(line) && notElse;
249                    boolean isTrailingCondition = TrailingConditionRegExp.instance.isMatch(line);
250
251//                    Macros.message(area, line + " "
252//                            + isDoStatement + " " + isSyntaxStatement + " " + isTrailingCondition);
253
254                    if (isDoStatement || isSyntaxStatement || isTrailingCondition) {
255                        if (endCount > 0) {
256                            endCount--;
257                        } else {
258                            InsertRegularExpression re = isDoStatement ? DoRegExp.instance : MatchRegExp.instance;
259                            re = isTrailingCondition ? TrailingConditionRegExp.instance : re;
260                            String indent = re.indent(line);
261                            reIndent(trimLine, indent);
262
263                            MatchResult matches = re.firstMatch(line);
264                            area.selectLine();
265                            area.setSelectedText(matches.group(1));
266                            break;
267                        }
268                    }
269                }
270            }
271        }
272    }
273
274    private static boolean isDoInComment(String line) {
275        boolean inComment = false;
276        int commentIndex = line.indexOf("#");
277        int paramIndex = line.indexOf("#{");
278        boolean hasHash = commentIndex != -1;
279        boolean hashNotParamStart = commentIndex != paramIndex;
280
281        if (hasHash && hashNotParamStart) {
282            int doIndex = line.indexOf(" do ");
283            if (doIndex > commentIndex) {
284                inComment = true;
285            }
286        }
287        return inComment;
288    }
289
290    private void handleInsertEnd(InsertRegularExpression re, String line) {
291        String indent = re.indent(line);
292
293        area.insertEnterAndIndent();
294        area.selectLine();
295        area.setSelectedText(indent + "end");
296        if (endsNotBalanced()) {
297            area.deleteLine();
298        }
299
300        area.goToPrevLine(false);
301        area.goToEndOfWhiteSpace(false);
302        area.insertEnterAndIndent();
303        area.selectLine();
304        String text = area.getSelectedText() != null ? area.getSelectedText().trim() : "";
305        area.setSelectedText(indent + text);
306
307        if(!line.endsWith("end")) {
308            area.shiftIndentRight();
309        }
310    }
311
312    private boolean endsNotBalanced() {
313        String line;
314        int count = area.getLineCount();
315        int balancedCount = 0;
316//        StringBuffer buffer = new StringBuffer("");
317        boolean isString = false;
318
319        for (int i = 0; i < count; i++) {
320            line = area.getLineText(i).trim();
321            if (hasEndKeyword(line)) {
322                int endCount = getEndCount(line);
323                while (endCount != 0) {
324                    balancedCount--;
325//                    buffer.append(balancedCount).append("");
326//                    for (int j = 0; j < balancedCount; buffer.append(j++ > -1 ? "    " : ""));
327//                    buffer.append(line).append("\n");
328                    endCount--;
329                }
330            }
331            if (line.indexOf("<<-EOF") != -1) {
332                isString = true;
333            } else if (line.indexOf("EOF") != -1) {
334                isString = false;
335            }
336            if (!isString) {
337                boolean isDoMatch = DoRegExp.instance.isMatch(line);
338                boolean doInComment = isDoInComment(line);
339//                if(line.indexOf("File.open") != -1) {
340//                    buffer.append("do: ").append(isDoMatch).append(", in comment: ").append(doInComment).append(',').append(line).append('\n');
341//                }
342                boolean isDoStatement = isDoMatch && !doInComment;
343                boolean ignore = IgnoreRegExp.instance.isMatch(line);
344                boolean conditionalAssignment = TrailingConditionRegExp.instance.isMatch(line);
345
346                if (conditionalAssignment || (!ignore && (isDoStatement || MatchRegExp.instance.isMatch(line)))) {
347                    boolean openingBrace = line.indexOf("{") != -1 && line.indexOf("}") == -1;
348                    boolean elsif = line.indexOf("elsif") != -1;
349                    if (!openingBrace && !elsif) {
350                        balancedCount++;
351//                        buffer.append(balancedCount).append("");
352//                        for (int j = 0; j < balancedCount; buffer.append(j++ > -1 ? "    " : ""));
353//                        buffer.append(line).append("\n");
354
355                        int moduleIndex = line.indexOf("module");
356                        while(moduleIndex != -1) {
357                            moduleIndex = line.indexOf("module", moduleIndex+5);
358                            if(moduleIndex != -1) {
359                                balancedCount++;
360//                                buffer.append(balancedCount).append("");
361//                                for (int j = 0; j < balancedCount; buffer.append(j++ > -1 ? "    " : ""));
362//                                buffer.append(line).append("\n");
363                            }
364                        }
365
366                        moduleIndex = line.indexOf("module");
367
368                        if(moduleIndex != -1) {
369                            int classIndex = line.indexOf("class", moduleIndex+5);
370                            if(classIndex != -1) {
371                                balancedCount++;
372                            }
373                        }
374
375                        int classIndex = line.indexOf("class");
376//                        buffer.append("balance: ").append(balancedCount).append("\n");
377//                        buffer.append("classIndex: ").append(classIndex).append("\n");
378                        if (classIndex != -1) {
379                            int defIndex = line.indexOf("def", classIndex+4);
380//                            buffer.append("defIndex: ").append(defIndex).append("\n");
381                            while(defIndex != -1) {
382                                balancedCount++;
383//                                buffer.append(balancedCount).append("");
384//                                for (int j = 0; j < balancedCount; buffer.append(j++ > -1 ? "    " : ""));
385//                                buffer.append(line).append("\n");
386                                defIndex = line.indexOf("def", defIndex+2);
387                            }
388                        }
389                    }
390                }
391            }
392        }
393
394//        RubyPlugin.log(buffer.toString(), AutoIndentAndInsertEnd.class);
395        boolean endsNotBalanced = balancedCount < 0;
396        RubyPlugin.log("Ends " + (endsNotBalanced ? "not " : "") + "balanced: " + balancedCount, AutoIndentAndInsertEnd.class);
397        return endsNotBalanced;
398    }
399
400    public static int getEndCount(String line) {
401//        return SimpleEndRegExp.instance.getAllMatches(line).length;
402        return SimpleEndRegExp.instance.allMatchResults(line);
403    }
404
405    public static boolean hasEndKeyword(String line) {
406        return EnhancedEndRegExp.instance.isMatch(line) ||
407                EnhancedEndRegExp2.instance.isMatch(line) ||
408                (line.trim().endsWith("end") && line.indexOf("#") == -1);
409    }
410
411    public static abstract class InsertRegularExpression extends RegularExpression {
412        String indent(String line) {
413            return firstMatch(line).group(1);
414        }
415    }
416
417    private static abstract class IndentRegularExpression extends InsertRegularExpression {
418        final String indent(String line) {
419            MatchResult match = instance().firstMatch(line);
420            StringBuffer indent = new StringBuffer(match.group(1));
421
422            if(extraIndent(line)) {
423                for (int i = 0; i < match.group(2).length(); i++) {
424                    indent.append(" ");
425                }
426            }
427
428            return indent.toString();
429        }
430
431        protected abstract RegularExpression instance();
432
433        boolean extraIndent(String line) {
434            return true;
435        }
436    }
437
438    /**
439     * matches other syntax that requires end
440     */
441    public static final class MatchRegExp extends IndentRegularExpression {
442        public static final InsertRegularExpression instance = new MatchRegExp();
443        private static final String indent = "(\\s*)";
444        private static final String leadingText = "([^#]*)";
445        private static final String trailingSpace = "\\s*";
446        public static final String expression = indent + leadingText
447                + "("
448                + "((if|for|while|until|unless|def|case|class|module)((?:\\s|\\()\\s*\\S+)+)"
449                + "|"
450                + "(begin|loop[ ]do|do)"
451                + ")" + trailingSpace;
452
453        protected final String getPattern() {
454            return expression;
455        }
456
457        protected final RegularExpression instance() {
458            return instance;
459        }
460
461        final boolean extraIndent(String line) {
462            return line.indexOf("begin") == -1 && line.indexOf("do") == -1;
463        }
464    }
465
466    /**
467     * matches lines to ignore
468     */
469    public static final class IgnoreRegExp extends InsertRegularExpression {
470        public static final RegularExpression instance = new IgnoreRegExp();
471        protected final String getPattern() {
472            return "((.*)(" +
473//                    "([[:graph:]]\\s+(if|unless)(\\s+\\S+)+)" +
474                    "([^ \\t\\n\\r\\f\\v]\\s+(if|unless)(\\s+\\S+)+)" +
475                    ")\\s*)" +
476                    "|" +
477                    "([^\"]*(\"|')[^\"]*" +
478                    "(if|for|while|until|unless|def|case|class|module|do|begin|loop[ ]do)" +
479                    "[^\"]*(\"|')[^\"]*)";
480        }
481    }
482
483    /**
484     * matches x.y do |z| expressions
485     */
486    private static final class DoRegExp extends InsertRegularExpression {
487        private static final InsertRegularExpression instance = new DoRegExp();
488        protected final String getPattern() {
489            return "(\\s*)(\\S+\\s+)+do\\s+\\|+[^\\|]*\\|\\s*";
490        }
491    }
492
493    private static final class SimpleEndRegExp extends InsertRegularExpression {
494        private static final RegularExpression instance = new SimpleEndRegExp();
495        protected final String getPattern() {
496            return "end";
497        }
498    }
499
500    private static final class EndRegExp extends InsertRegularExpression {
501        private static final RegularExpression instance = new EndRegExp();
502        protected final String getPattern() {
503            return "[^#]*end\\s*";
504        }
505    }
506
507    private static final class EnhancedEndRegExp extends InsertRegularExpression {
508        private static final RegularExpression instance = new EnhancedEndRegExp();
509        protected final String getPattern() {
510            return "^end(\\s*|(\\s+.*))";
511        }
512    }
513
514    private static final class EnhancedEndRegExp2 extends InsertRegularExpression {
515        private static final RegularExpression instance = new EnhancedEndRegExp();
516        protected final String getPattern() {
517            return "[^#]*\\s+end(\\s*|(\\s+.*))";
518        }
519    }
520
521    private static final class CommentRegExp extends InsertRegularExpression {
522        private static final RegularExpression instance = new CommentRegExp();
523        protected final String getPattern() {
524            return "(\\s*)(##?)(.*)";
525        }
526    }
527
528    public static final class TrailingConditionRegExp extends IndentRegularExpression {
529        public static final InsertRegularExpression instance = new TrailingConditionRegExp();
530        protected final String getPattern() {
531            return "(\\s*)([^#]*=\\s*)(((if)|(unless)|(case)).*)";
532        }
533
534        protected final RegularExpression instance() {
535            return instance;
536        }
537    }
538
539    private static final class IfElsifRegExp extends InsertRegularExpression {
540        private static final RegularExpression instance = new IfElsifRegExp();
541        protected final String getPattern() {
542            return "(\\s*)((if)|(elsif))(.*)";
543        }
544    }
545
546//    private static class WhenRegExp extends RegularExpression {
547//        private static final RE instance = new WhenRegExp();
548//        protected String getPattern() {
549//            return "(\\s*)(when)(.*)";
550//        }
551//    }
552
553}