001: /*
002: * PerlMode.java
003: *
004: * Copyright (C) 1998-2003 Peter Graves
005: * $Id: PerlMode.java,v 1.2 2003/12/04 12:51:03 piso Exp $
006: *
007: * This program is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU General Public License
009: * as published by the Free Software Foundation; either version 2
010: * of the License, or (at your option) any later version.
011: *
012: * This program is distributed in the hope that it will be useful,
013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
015: * GNU General Public License for more details.
016: *
017: * You should have received a copy of the GNU General Public License
018: * along with this program; if not, write to the Free Software
019: * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
020: */
021:
022: package org.armedbear.j;
023:
024: import gnu.regexp.RE;
025: import gnu.regexp.UncheckedRE;
026: import java.awt.event.KeyEvent;
027:
028: public final class PerlMode extends AbstractMode implements Constants,
029: Mode {
030: private static final PerlMode mode = new PerlMode();
031:
032: private PerlMode() {
033: super (PERL_MODE, PERL_MODE_NAME);
034: keywords = new Keywords(this );
035: }
036:
037: public static final PerlMode getMode() {
038: return mode;
039: }
040:
041: public final boolean canIndent() {
042: return true;
043: }
044:
045: public final String getCommentStart() {
046: return "# ";
047: }
048:
049: public final SyntaxIterator getSyntaxIterator(Position pos) {
050: return new PerlSyntaxIterator(pos);
051: }
052:
053: public final Formatter getFormatter(Buffer buffer) {
054: return new PerlFormatter(buffer);
055: }
056:
057: protected void setKeyMapDefaults(KeyMap km) {
058: km.mapKey('{', "electricOpenBrace");
059: km.mapKey('}', "electricCloseBrace");
060: km.mapKey(';', "electricSemi");
061: km.mapKey(KeyEvent.VK_TAB, 0, "tab");
062: km.mapKey(KeyEvent.VK_ENTER, 0, "newlineAndIndent");
063: km.mapKey(KeyEvent.VK_T, CTRL_MASK, "findTag");
064: km.mapKey(KeyEvent.VK_PERIOD, ALT_MASK, "findTagAtDot");
065: km.mapKey(KeyEvent.VK_L, CTRL_MASK | SHIFT_MASK, "listTags");
066: km.mapKey(')', "closeParen");
067: km.mapKey(KeyEvent.VK_I, ALT_MASK, "cycleIndentSize");
068:
069: km.mapKey(KeyEvent.VK_OPEN_BRACKET, CTRL_MASK | SHIFT_MASK,
070: "insertBraces");
071: // Duplicate mapping for 1.4.
072: km.mapKey(KeyEvent.VK_BRACELEFT, CTRL_MASK | SHIFT_MASK,
073: "insertBraces");
074:
075: km.mapKey(KeyEvent.VK_9, CTRL_MASK | SHIFT_MASK,
076: "insertParentheses");
077:
078: km.mapKey(KeyEvent.VK_F12, 0, "wrapComment");
079:
080: if (Platform.isPlatformLinux()) {
081: // Blackdown 1.1.7v3, 1.2pre2, IBM 1.1.8.
082: // Duplicate mappings needed for VK_9, VK_0 and VK_OPEN_BRACKET.
083: km.mapKey(0xbb, CTRL_MASK | SHIFT_MASK, "insertBraces");
084:
085: // Duplicate mapping to support IBM 1.3 for Linux.
086: km.mapKey(0xffc9, 0, "wrapComment"); // F12
087: }
088:
089: km.mapKey(KeyEvent.VK_OPEN_BRACKET, CTRL_MASK, "fold");
090: km.mapKey(KeyEvent.VK_CLOSE_BRACKET, CTRL_MASK, "unfold");
091: }
092:
093: public Tagger getTagger(SystemBuffer buffer) {
094: return new PerlTagger(buffer);
095: }
096:
097: public boolean isTaggable() {
098: return true;
099: }
100:
101: public boolean hasQualifiedNames() {
102: return true;
103: }
104:
105: public boolean isQualifiedName(String s) {
106: return s.indexOf("::") >= 0;
107: }
108:
109: public int getCorrectIndentation(Line line, Buffer buffer) {
110: String trim = line.trim();
111: if (trim.length() > 0) {
112: Position pos = null;
113: char c = trim.charAt(0);
114: if (c == '}') {
115: pos = matchClosingBrace(new Position(line, line
116: .getText().indexOf('}')));
117: if (pos == null)
118: return 0;
119: if (!pos.getLine().trim().startsWith("{"))
120: pos = findBeginningOfStatement(pos);
121: } else if (c == ')') {
122: pos = findEnclosingParen(new Position(line, line
123: .getText().indexOf(')')));
124: if (pos == null)
125: return 0;
126: if (!pos.getLine().trim().startsWith("("))
127: pos = findBeginningOfStatement(pos);
128: }
129: if (pos != null)
130: return buffer.getIndentation(pos.getLine());
131:
132: // Labels are a special case.
133: if (isLabel(line))
134: return 0;
135: }
136: final Line modelLine = findModel(line);
137: if (modelLine == null)
138: return 0;
139: final int indentSize = buffer.getIndentSize();
140: int modelIndent = 0;
141: final String modelText = trimSyntacticWhitespace(modelLine
142: .getText());
143: if (modelText.equals("{")) {
144: modelIndent = buffer.getIndentation(modelLine);
145: if (buffer.getBooleanProperty(Property.INDENT_AFTER_BRACE))
146: return modelIndent + indentSize;
147: else
148: return modelIndent;
149: }
150: Position pos = findBeginningOfStatement(new Position(modelLine,
151: 0));
152: if (pos != null)
153: modelIndent = buffer.getIndentation(pos.getLine());
154: if (modelText.endsWith("{")) {
155: if (buffer.getBooleanProperty(Property.INDENT_AFTER_BRACE))
156: return modelIndent + indentSize;
157: else
158: return modelIndent;
159: }
160: if (modelText.endsWith("}"))
161: return modelIndent;
162: if (modelText.endsWith(";"))
163: return modelIndent;
164: pos = findEnclosingParen(new Position(line, 0));
165: if (pos != null) {
166: if (pos.getLine().trim().endsWith("(")
167: || !buffer
168: .getBooleanProperty(Property.LINEUP_ARGLIST)) {
169: return buffer.getIndentation(pos.getLine())
170: + indentSize;
171: } else {
172: // Advance past '('.
173: pos.skip(1);
174: // Advance to first non-whitespace char.
175: pos.skipWhitespaceOnCurrentLine();
176: return buffer.getCol(pos);
177: }
178: }
179: if (modelText.endsWith(","))
180: return buffer.getIndentation(modelLine);
181:
182: // Continuation line.
183: pos = findBeginningOfStatement(new Position(line, 0));
184:
185: if (pos != null) {
186: if (line.getText().trim().startsWith("{")) {
187: if (buffer
188: .getBooleanProperty(Property.INDENT_BEFORE_BRACE))
189: return buffer.getIndentation(pos.getLine())
190: + indentSize;
191: else
192: return buffer.getIndentation(pos.getLine());
193: } else
194: return buffer.getIndentation(pos.getLine())
195: + indentSize;
196: }
197: return modelIndent;
198: }
199:
200: private static Line findModel(Line line) {
201: for (Line modelLine = line.previous(); modelLine != null; modelLine = modelLine
202: .previous()) {
203: if (modelLine.isBlank())
204: continue;
205: else if (modelLine.trim().startsWith("#"))
206: continue;
207: else if (isLabel(modelLine))
208: continue;
209: else
210: return modelLine;
211: }
212: return null;
213: }
214:
215: private static RE labelRE = new UncheckedRE(
216: "^\\s*[A-Za-z0-9_]+:\\s*$");
217:
218: private static boolean isLabel(Line line) {
219: return labelRE.getMatch(line.getText()) != null;
220: }
221:
222: // Scan backwards from starting position, looking for unmatched opening
223: // parenthesis.
224: private static Position findEnclosingParen(Position start) {
225: PerlSyntaxIterator it = new PerlSyntaxIterator(start);
226: int count = 0;
227: char c;
228: while ((c = it.prevChar()) != SyntaxIterator.DONE) {
229: if (c == '}')
230: return null;
231: if (c == ')') {
232: ++count;
233: } else if (c == '(') {
234: if (count == 0)
235: return it.getPosition(); // Found unmatched '('.
236: else
237: --count;
238: }
239: }
240: return null;
241: }
242:
243: static Position findBeginningOfStatement(Position start) {
244: Position pos = new Position(start);
245: if (pos.getLine().trim().startsWith("}")) {
246: Position posMatch = matchClosingBrace(new Position(pos
247: .getLine(), 0));
248: if (posMatch != null)
249: pos = posMatch;
250: } else {
251: Position posParen = findEnclosingParen(pos);
252: if (posParen != null)
253: pos = posParen;
254: }
255: while (pos.getLine() != null) {
256: pos.setLine(pos.getLine().previous());
257: if (pos.getLine() == null)
258: break;
259: pos.setOffset(pos.getLineLength());
260: String s = trimSyntacticWhitespace(pos.getLine().getText());
261: if (s.length() > 0) {
262: char c = s.charAt(s.length() - 1);
263: if (c == ';' || c == '{' || c == '}' || c == ':')
264: break;
265: }
266: }
267: if (pos.getLine() == null) {
268: pos.moveTo(start.getLine(), 0);
269: } else {
270: // Skip syntactic whitespace.
271: PerlSyntaxIterator it = new PerlSyntaxIterator(pos);
272: while (true) {
273: char c = it.nextChar();
274: if (c == SyntaxIterator.DONE)
275: break;
276: if (c > ' ')
277: break;
278: }
279: pos = it.getPosition();
280: }
281: return pos;
282: }
283:
284: private static Position matchClosingBrace(Position start) {
285: int count = 1;
286: PerlSyntaxIterator it = new PerlSyntaxIterator(start);
287: char c;
288: while ((c = it.prevChar()) != SyntaxIterator.DONE) {
289: if (c == '}')
290: ++count;
291: else if (c == '{')
292: --count;
293: if (count == 0) // Found it!
294: return it.getPosition();
295: }
296: return null;
297: }
298:
299: // Replace syntactic whitespace (quotes and comments) with actual space
300: // characters and return trimmed string.
301: protected static String trimSyntacticWhitespace(String s) {
302: PerlSyntaxIterator it = new PerlSyntaxIterator(null);
303: return (new String(it.hideSyntacticWhitespace(s))).trim();
304: }
305:
306: private static final String validChars = "$@%ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_0123456789";
307:
308: static final boolean isIdentifierChar(char c) {
309: return (validChars.indexOf(c) >= 0);
310: }
311:
312: public boolean isIdentifierStart(char c) {
313: return isIdentifierChar(c);
314: }
315:
316: public boolean isIdentifierPart(char c) {
317: return isIdentifierChar(c);
318: }
319:
320: public boolean isCommentLine(Line line) {
321: return line.trim().startsWith("#");
322: }
323: }
|