001: /*
002: * RubyIndenter.java
003: *
004: * Copyright (C) 2002 Jens Luedicke <jens@irs-net.com>
005: * based on PythonIndenter.java
006: * $Id: RubyIndenter.java,v 1.1.1.1 2002/09/24 16:08:22 piso Exp $
007: *
008: * This program is free software; you can redistribute it and/or
009: * modify it under the terms of the GNU General Public License
010: * as published by the Free Software Foundation; either version 2
011: * of the License, or (at your option) any later version.
012: *
013: * This program is distributed in the hope that it will be useful,
014: * but WITHOUT ANY WARRANTY; without even the implied warranty of
015: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
016: * GNU General Public License for more details.
017: *
018: * You should have received a copy of the GNU General Public License
019: * along with this program; if not, write to the Free Software
020: * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
021: */
022:
023: package org.armedbear.j;
024:
025: public final class RubyIndenter {
026: private static final RubyMode mode = RubyMode.getMode();
027:
028: private final Line line;
029: private final Buffer buffer;
030: private final int indentSize;
031:
032: public RubyIndenter(Line line, Buffer buffer) {
033: this .line = line;
034: this .buffer = buffer;
035: indentSize = buffer.getIndentSize();
036: }
037:
038: public int getCorrectIndentation() {
039: final String lineFirst = getFirstIdentifier(line);
040: if (lineFirst.equals("def"))
041: return indentDef();
042: if (lineFirst.equals("when"))
043: return indentWhen();
044: if (lineFirst.equals("end"))
045: return indentEnd();
046: final Line model = findModel(line);
047: if (model == null)
048: return 0;
049: final int modelIndent = buffer.getIndentation(model);
050: final String lineText = trimSyntacticWhitespace(line.getText());
051: if (lineText.startsWith("}"))
052: return Math.max(modelIndent - indentSize, 0);
053: final String modelText = trimSyntacticWhitespace(model
054: .getText());
055: if (modelText.endsWith("do") || modelText.endsWith("|")) {
056: // e.g. "do |foo|"
057: return modelIndent + indentSize;
058: }
059: final String modelFirst = getFirstIdentifier(modelText);
060: final String[] indentAfter = { "begin", "class", "def", "if",
061: "else", "elsif", "for", "module", "unless", "when",
062: "while" };
063: if (Utilities.isOneOf(modelFirst, indentAfter))
064: return modelIndent + indentSize;
065: // Unindent if the current line starts with "else" or "elsif".
066: final String[] unindent = { "else", "elsif" };
067: if (Utilities.isOneOf(lineFirst, unindent))
068: return Math.max(0, modelIndent - indentSize);
069: return modelIndent;
070: }
071:
072: // Scan backwards for line starting with "def" or "class" and indent
073: // accordingly.
074: private int indentDef() {
075: for (Line model = line.previous(); model != null; model = model
076: .previous()) {
077: String modelFirst = getFirstIdentifier(model);
078: if (modelFirst.equals("def"))
079: return buffer.getIndentation(model);
080: if (modelFirst.equals("class"))
081: return buffer.getIndentation(model)
082: + buffer.getIndentSize();
083: }
084: return 0;
085: }
086:
087: // Scan backwards for line starting with "when" or "case" and indent
088: // accordingly. This doesn't work correctly with nested case statements!
089: private int indentWhen() {
090: for (Line model = line.previous(); model != null; model = model
091: .previous()) {
092: String modelFirst = getFirstIdentifier(model);
093: if (modelFirst.equals("when") || modelFirst.equals("case"))
094: return buffer.getIndentation(model);
095: }
096: return 0;
097: }
098:
099: private int indentEnd() {
100: for (Line model = line.previous(); model != null; model = model
101: .previous()) {
102: if (model.isBlank() || model.trim().startsWith("#"))
103: continue;
104: String modelFirst = getFirstIdentifier(model);
105: if (modelFirst.equals("def"))
106: return buffer.getIndentation(model);
107: return Math.max(buffer.getIndentation(model) - indentSize,
108: 0);
109: }
110: return 0;
111: }
112:
113: // Return last non-blank line before this one.
114: private static Line findModel(Line line) {
115: for (Line model = line.previous(); model != null; model = model
116: .previous()) {
117: if (!model.isBlank() && !model.trim().startsWith("#"))
118: return model;
119: }
120: return null;
121: }
122:
123: // Replace syntactic whitespace (quotes and comments) with actual space
124: // characters and return trimmed string.
125: private static String trimSyntacticWhitespace(String s) {
126: RubySyntaxIterator it = new RubySyntaxIterator(null);
127: return new String(it.hideSyntacticWhitespace(s)).trim();
128: }
129:
130: // Never returns null.
131: private static String getFirstIdentifier(Line line) {
132: return getFirstIdentifier(trimSyntacticWhitespace(line
133: .getText()));
134: }
135:
136: // Never returns null.
137: private static String getFirstIdentifier(String s) {
138: FastStringBuffer sb = new FastStringBuffer();
139: final int length = s.length();
140: int i = 0;
141: while (i < length && Character.isWhitespace(s.charAt(i)))
142: ++i;
143: char c;
144: while (i < length && !Character.isWhitespace(c = s.charAt(i))) {
145: sb.append(c);
146: ++i;
147: }
148: return sb.toString();
149: }
150: }
|