0001: /*
0002: * JavaMode.java
0003: *
0004: * Copyright (C) 1998-2004 Peter Graves
0005: * $Id: JavaMode.java,v 1.16 2004/09/08 00:48:28 piso Exp $
0006: *
0007: * This program is free software; you can redistribute it and/or
0008: * modify it under the terms of the GNU General Public License
0009: * as published by the Free Software Foundation; either version 2
0010: * of the License, or (at your option) any later version.
0011: *
0012: * This program is distributed in the hope that it will be useful,
0013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
0014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
0015: * GNU General Public License for more details.
0016: *
0017: * You should have received a copy of the GNU General Public License
0018: * along with this program; if not, write to the Free Software
0019: * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
0020: */
0021:
0022: package org.armedbear.j;
0023:
0024: import gnu.regexp.RE;
0025: import gnu.regexp.REMatch;
0026: import gnu.regexp.UncheckedRE;
0027: import java.awt.event.KeyEvent;
0028: import java.awt.event.MouseEvent;
0029: import javax.swing.JMenuItem;
0030: import javax.swing.JPopupMenu;
0031: import javax.swing.undo.CompoundEdit;
0032:
0033: public class JavaMode extends AbstractMode implements Constants, Mode {
0034: private static final String[] javaConditionals = { "if", "else",
0035: "do", "while", "for", "switch", "try", "catch", "finally",
0036: "synchronized" };
0037:
0038: private static Mode mode;
0039: private static Object jdb;
0040:
0041: protected String[] conditionals;
0042:
0043: private JavaMode() {
0044: super (JAVA_MODE, JAVA_MODE_NAME);
0045: keywords = new Keywords(this );
0046: conditionals = javaConditionals;
0047: }
0048:
0049: protected JavaMode(int id, String displayName) {
0050: super (id, displayName);
0051: }
0052:
0053: // Don't construct the singleton class instance until we actually need it,
0054: // to avoid unnecessary overhead for the derived classes.
0055: public static Mode getMode() {
0056: if (mode == null)
0057: mode = new JavaMode();
0058: return mode;
0059: }
0060:
0061: public static final Object getJdb() {
0062: return jdb;
0063: }
0064:
0065: public static final void setJdb(Object obj) {
0066: jdb = obj;
0067: }
0068:
0069: public boolean canIndent() {
0070: return true;
0071: }
0072:
0073: public SyntaxIterator getSyntaxIterator(Position pos) {
0074: return new JavaSyntaxIterator(pos);
0075: }
0076:
0077: public String getCommentStart() {
0078: return "// ";
0079: }
0080:
0081: public Formatter getFormatter(Buffer buffer) {
0082: return new JavaFormatter(buffer);
0083: }
0084:
0085: protected void setKeyMapDefaults(KeyMap km) {
0086: km.mapKey('{', "electricOpenBrace");
0087: km.mapKey('}', "electricCloseBrace");
0088: km.mapKey(KeyEvent.VK_TAB, CTRL_MASK, "insertTab");
0089: km.mapKey(KeyEvent.VK_TAB, 0, "tab");
0090: km.mapKey(KeyEvent.VK_ENTER, 0, "newlineAndIndent");
0091: km.mapKey(';', "electricSemi");
0092: km.mapKey(':', "electricColon");
0093: km.mapKey('*', "electricStar");
0094: km.mapKey(KeyEvent.VK_T, CTRL_MASK, "findTag");
0095: km.mapKey(KeyEvent.VK_PERIOD, ALT_MASK, "findTagAtDot");
0096: km.mapKey(KeyEvent.VK_COMMA, ALT_MASK, "listMatchingTagsAtDot");
0097: km.mapKey(KeyEvent.VK_PERIOD, CTRL_MASK | ALT_MASK,
0098: "findTagAtDotOtherWindow");
0099: km.mapKey(')', "closeParen");
0100: km.mapKey(KeyEvent.VK_I, ALT_MASK, "cycleIndentSize");
0101:
0102: km.mapKey(KeyEvent.VK_9, CTRL_MASK | SHIFT_MASK,
0103: "insertParentheses");
0104: km.mapKey(KeyEvent.VK_0, CTRL_MASK | SHIFT_MASK,
0105: "movePastCloseAndReindent");
0106:
0107: km.mapKey(KeyEvent.VK_OPEN_BRACKET, CTRL_MASK | SHIFT_MASK,
0108: "insertBraces");
0109: // Duplicate mapping for 1.4.
0110: km.mapKey(KeyEvent.VK_BRACELEFT, CTRL_MASK | SHIFT_MASK,
0111: "insertBraces");
0112:
0113: km.mapKey(KeyEvent.VK_F12, 0, "wrapComment");
0114:
0115: // Duplicate mapping to support IBM 1.3 for Linux.
0116: km.mapKey(0xffc9, 0, "wrapComment"); // F12
0117:
0118: km.mapKey(KeyEvent.VK_OPEN_BRACKET, CTRL_MASK, "fold");
0119: km.mapKey(KeyEvent.VK_CLOSE_BRACKET, CTRL_MASK, "unfold");
0120:
0121: km.mapKey(KeyEvent.VK_F9, 0, "compile");
0122: km.mapKey(KeyEvent.VK_F9, CTRL_MASK, "recompile");
0123: km.mapKey(KeyEvent.VK_F1, ALT_MASK, "jdkHelp");
0124: km.mapKey(KeyEvent.VK_F1, CTRL_MASK, "source");
0125:
0126: // This is the "normal" mapping.
0127: km.mapKey(KeyEvent.VK_COMMA, CTRL_MASK | SHIFT_MASK,
0128: "htmlInsertTag");
0129: // The "normal" mapping doesn't work for Linux, but this does.
0130: km.mapKey(0x7c, CTRL_MASK | SHIFT_MASK, "htmlInsertTag");
0131:
0132: if (Editor.checkExperimental()) {
0133: km.mapKey(KeyEvent.VK_SEMICOLON, ALT_MASK,
0134: "JavaMode.insertComment");
0135: km.mapKey(KeyEvent.VK_ENTER, ALT_MASK,
0136: "JavaMode.newlineAndIndentForComment");
0137: }
0138:
0139: if (Platform.isPlatformLinux()) {
0140: // Blackdown 1.1.7v3, 1.2pre2, IBM 1.1.8.
0141: // Duplicate mappings needed for VK_9, VK_0 and VK_OPEN_BRACKET.
0142: km
0143: .mapKey(0x68, CTRL_MASK | SHIFT_MASK,
0144: "insertParentheses");
0145: km.mapKey(0x69, CTRL_MASK | SHIFT_MASK,
0146: "movePastCloseAndReindent");
0147: km.mapKey(0xbb, CTRL_MASK | SHIFT_MASK, "insertBraces");
0148: }
0149: }
0150:
0151: public void populateModeMenu(Editor editor, Menu menu) {
0152: menu.add(editor, "Compile...", 'C', "compile");
0153: menu.add(editor, "Recompile", 'R', "recompile");
0154: boolean enabled = CompilationCommands.getCompilationBuffer() != null;
0155: menu.addSeparator();
0156: menu.add(editor, "Next Error", 'N', "nextError", enabled);
0157: menu.add(editor, "Previous Error", 'P', "previousError",
0158: enabled);
0159: menu.add(editor, "Show Error Message", 'M', "showMessage",
0160: enabled);
0161: menu.addSeparator();
0162: MenuItem jdbMenuItem = menu.add(editor, "Debug...", 'D', "jdb");
0163: if (jdb != null)
0164: jdbMenuItem.setEnabled(false);
0165: else {
0166: try {
0167: Class.forName("com.sun.jdi.Bootstrap");
0168: } catch (ClassNotFoundException e) {
0169: jdbMenuItem.setEnabled(false);
0170: }
0171: }
0172: }
0173:
0174: public JPopupMenu getContextMenu(Editor editor) {
0175: final JPopupMenu popup = new JPopupMenu();
0176: if (jdb != null) {
0177: final Line line = editor.getDotLine();
0178: if (line != null) {
0179: final Dispatcher dispatcher = editor.getDispatcher();
0180: JMenuItem menuItem = addContextMenuItem(
0181: "Set breakpoint", "jdbSetBreakpoint", popup,
0182: dispatcher);
0183: if (line.isBlank() || line.getAnnotation() != null)
0184: menuItem.setEnabled(false);
0185: menuItem = addContextMenuItem("Delete breakpoint",
0186: "jdbDeleteBreakpoint", popup, dispatcher);
0187: if (line.getAnnotation() == null)
0188: menuItem.setEnabled(false);
0189: menuItem = addContextMenuItem("Run to current line",
0190: "jdbRunToCurrentLine", popup, dispatcher);
0191: if (line.isBlank())
0192: menuItem.setEnabled(false);
0193: popup.addSeparator();
0194: }
0195: }
0196: addDefaultContextMenuItems(editor, popup);
0197: popup.pack();
0198: return popup;
0199: }
0200:
0201: public NavigationComponent getSidebarComponent(Editor editor) {
0202: if (getId() == JAVA_MODE) {
0203: View view = editor.getCurrentView();
0204: if (view == null)
0205: return null; // Shouldn't happen.
0206: if (view.getSidebarComponent() == null)
0207: view.setSidebarComponent(new JavaTree(editor));
0208: return view.getSidebarComponent();
0209: }
0210: // For subclasses...
0211: return super .getSidebarComponent(editor);
0212: }
0213:
0214: public Tagger getTagger(SystemBuffer buffer) {
0215: return new JavaTagger(buffer);
0216: }
0217:
0218: public boolean isTaggable() {
0219: return true;
0220: }
0221:
0222: public boolean hasQualifiedNames() {
0223: return true;
0224: }
0225:
0226: public boolean isQualifiedName(String s) {
0227: return s.indexOf('.') >= 0;
0228: }
0229:
0230: public int getCorrectIndentation(final Line line,
0231: final Buffer buffer) {
0232: if (line.flags() == STATE_COMMENT)
0233: return indentComment(line, buffer);
0234: final String text = line.trim();
0235: final char textFirstChar = text.length() > 0 ? text.charAt(0)
0236: : 0;
0237: if (textFirstChar == '}')
0238: return indentClosingBrace(line, buffer);
0239:
0240: if (textFirstChar == 'c' || textFirstChar == 'd') {
0241: // Does line begin with "case" or "default"?
0242: final String firstIdentifier = getFirstIdentifier(text);
0243: if (firstIdentifier.equals("case")
0244: || firstIdentifier.equals("default"))
0245: return indentSwitchLabel(line, buffer);
0246: // Otherwise fall through...
0247: } else if (textFirstChar == 'e') {
0248: // Does line begin with "else" or "elseif"?
0249: final String firstIdentifier = getFirstIdentifier(text);
0250: if (firstIdentifier.equals("else")
0251: || firstIdentifier.equals("elseif")) {
0252: Position match = matchElse(new Position(line, 0));
0253: if (match != null)
0254: return buffer.getIndentation(match.getLine());
0255: }
0256: // Otherwise fall through...
0257: }
0258:
0259: Position paren = findEnclosingParen(new Position(line, 0));
0260: if (paren != null)
0261: return indentInParen(paren, buffer);
0262:
0263: final Line model = findModel(line);
0264: if (model == null)
0265: return 0;
0266:
0267: final int indentSize = buffer.getIndentSize();
0268:
0269: final String firstIdentifier = getFirstIdentifier(text);
0270: if (firstIdentifier.equals("throws")
0271: || firstIdentifier.equals("implements")) {
0272: Position pos = findBeginningOfStatement(new Position(model,
0273: 0));
0274: return buffer.getIndentation(pos.getLine()) + indentSize;
0275: }
0276:
0277: final String modelText = trimSyntacticWhitespace(model
0278: .getText());
0279:
0280: // Model line can't be blank, so this is safe.
0281: final char modelLastChar = modelText
0282: .charAt(modelText.length() - 1);
0283:
0284: if (modelLastChar == '{')
0285: return indentAfterOpeningBrace(model, modelText, buffer);
0286:
0287: if (modelLastChar == ')')
0288: return indentAfterCloseParen(model, text, textFirstChar,
0289: buffer);
0290:
0291: final String lastIdentifier = getLastIdentifier(modelText);
0292: if (lastIdentifier != null && lastIdentifier.equals("else"))
0293: return indentAfterElse(model, text, textFirstChar, buffer);
0294:
0295: final char modelFirstChar = modelText.charAt(0);
0296: if (modelFirstChar == 'c' || modelFirstChar == 'd') {
0297: final String modelFirstIdentifier = getFirstIdentifier(modelText);
0298: if (modelFirstIdentifier.equals("case")
0299: || modelFirstIdentifier.equals("default"))
0300: return indentAfterSwitchLabel(model, text,
0301: textFirstChar, buffer);
0302: // Otherwise fall through...
0303: }
0304:
0305: final int indent = getIndentationOfEnclosingScope(line, buffer);
0306:
0307: if (textFirstChar == '{') {
0308: if (buffer.getBooleanProperty(Property.INDENT_BEFORE_BRACE)) {
0309: // Never indent before the opening brace of a class or method.
0310: if (!isOpeningBraceOfClassOrMethod(line))
0311: return indent + indentSize;
0312: }
0313: return indent;
0314: }
0315:
0316: if (modelLastChar == ',') {
0317: if (buffer.getModeId() == CPP_MODE && modelFirstChar == ':') {
0318: // Model line is start of member initialization list, current
0319: // line is continuation.
0320: return indent + 2;
0321: }
0322: if (isInArrayInitializer(line))
0323: return indent;
0324: // Otherwise it's a continuation line.
0325: return indent + indentSize;
0326: }
0327:
0328: // Check for continuation line.
0329: if (isContinued(modelText, modelLastChar))
0330: return indent + indentSize;
0331:
0332: return indent;
0333: }
0334:
0335: private final int indentComment(Line line, Buffer buffer) {
0336: final Line model = findModel(line);
0337: if (model == null)
0338: return 0;
0339: int indent = buffer.getIndentation(model);
0340: if (model.trim().startsWith("/*"))
0341: if (line.trim().startsWith("*"))
0342: return indent + 1;
0343: return indent;
0344: }
0345:
0346: private final int indentClosingBrace(Line line, Buffer buffer) {
0347: Position pos = matchClosingBrace(new Position(line, 0));
0348: if (isOpeningBraceOfClassOrMethod(pos.getLine()))
0349: pos = findBeginningOfStatement(pos);
0350: else if (!pos.getLine().trim().startsWith("{"))
0351: pos = findPreviousConditional(pos);
0352: return buffer.getIndentation(pos.getLine());
0353: }
0354:
0355: private final int indentSwitchLabel(Line line, Buffer buffer) {
0356: Line switchLine = findSwitch(line);
0357: if (switchLine != null)
0358: return buffer.getIndentation(switchLine)
0359: + buffer.getIndentSize();
0360: return 0;
0361: }
0362:
0363: private final int indentInParen(Position posParen, Buffer buffer) {
0364: final Line line = posParen.getLine();
0365: if (line.trim().endsWith("(")
0366: || !buffer.getBooleanProperty(Property.LINEUP_ARGLIST))
0367: return buffer.getIndentation(line) + buffer.getIndentSize();
0368: final int limit = line.length();
0369: int offset = posParen.getOffset();
0370: do {
0371: ++offset;
0372: } while (offset < limit && line.charAt(offset) <= ' ');
0373: if (offset <= limit)
0374: return buffer.getCol(line, offset);
0375: return 0;
0376: }
0377:
0378: private final int indentAfterOpeningBrace(Line model,
0379: String modelText, Buffer buffer) {
0380: final int indentSize = buffer.getIndentSize();
0381: if (isOpeningBraceOfClassOrMethod(model)) {
0382: Position pos = findBeginningOfStatement(new Position(model,
0383: 0));
0384: int indent = buffer.getIndentation(pos.getLine());
0385: if (buffer
0386: .getBooleanProperty(Property.INDENT_AFTER_OPENING_BRACE))
0387: indent += indentSize;
0388: return indent;
0389: }
0390: Position pos = new Position(model, model.length() - 1);
0391: if (modelText.charAt(0) != '{')
0392: pos = findPreviousConditional(pos);
0393: int indent = buffer.getIndentation(pos.getLine());
0394: if (buffer.getBooleanProperty(Property.INDENT_AFTER_BRACE))
0395: indent += indentSize;
0396: final boolean indentBeforeBrace = buffer
0397: .getBooleanProperty(Property.INDENT_BEFORE_BRACE);
0398: if (indentBeforeBrace && pos.getLine() != model)
0399: indent += indentSize;
0400: return indent;
0401: }
0402:
0403: private final int indentAfterElse(Line model, String text,
0404: char textFirstChar, Buffer buffer) {
0405: int indent = buffer.getIndentation(model);
0406: final boolean indentBeforeBrace = buffer
0407: .getBooleanProperty(Property.INDENT_BEFORE_BRACE);
0408: if (indentBeforeBrace || textFirstChar != '{')
0409: return indent + buffer.getIndentSize();
0410: else
0411: return indent;
0412: }
0413:
0414: private final int indentAfterSwitchLabel(Line model, String text,
0415: char textFirstChar, Buffer buffer) {
0416: int indent = buffer.getIndentation(model);
0417: final boolean indentBeforeBrace = buffer
0418: .getBooleanProperty(Property.INDENT_BEFORE_BRACE);
0419: if (indentBeforeBrace || textFirstChar != '{')
0420: return indent + buffer.getIndentSize();
0421: else
0422: return indent;
0423: }
0424:
0425: private final int indentAfterCloseParen(Line model, String text,
0426: char textFirstChar, Buffer buffer) {
0427: // Find matching '('.
0428: SyntaxIterator it = getSyntaxIterator(new Position(model, model
0429: .length()));
0430: char c;
0431: do {
0432: c = it.prevChar();
0433: } while (c != SyntaxIterator.DONE && c != ')');
0434: Position pos = it.getPosition();
0435: pos = matchClosingParen(pos);
0436: boolean indent = false;
0437: final String s = getIdentifierBefore(pos);
0438: final String[] indentAfter = { "if", "while", "for", "switch",
0439: "catch" };
0440: if (Utilities.isOneOf(s, indentAfter)) {
0441: indent = true;
0442: } else if (buffer.getModeId() == JAVA_MODE) {
0443: if (s.equals("synchronized"))
0444: indent = true;
0445: } else if (buffer.getModeId() == PHP_MODE) {
0446: if (s.equals("elseif") || s.equals("foreach"))
0447: indent = true;
0448: }
0449: if (indent) {
0450: int modelIndent = buffer.getIndentation(pos.getLine());
0451: if (textFirstChar != '{'
0452: || buffer
0453: .getBooleanProperty(Property.INDENT_BEFORE_BRACE))
0454: return modelIndent + buffer.getIndentSize();
0455: else
0456: return modelIndent;
0457: }
0458: if (buffer.getModeId() == JAVA_MODE) {
0459: RE re = new UncheckedRE("\\s+new\\s+");
0460: if (re.getMatch(pos.getLine().getText().substring(0,
0461: pos.getOffset())) != null)
0462: indent = true;
0463: }
0464: int modelIndent = buffer
0465: .getIndentation(findBeginningOfStatement(pos).getLine());
0466: if (indent
0467: && (textFirstChar != '{' || buffer
0468: .getBooleanProperty(Property.INDENT_BEFORE_BRACE)))
0469: return modelIndent + buffer.getIndentSize();
0470: else
0471: return modelIndent;
0472: }
0473:
0474: private final int getIndentationOfEnclosingScope(Line line,
0475: Buffer buffer) {
0476: SyntaxIterator it = getSyntaxIterator(new Position(line, 0));
0477: loop: while (true) {
0478: switch (it.prevChar()) {
0479: case ')': {
0480: Position pos = matchClosingParen(it.getPosition());
0481: it = getSyntaxIterator(pos);
0482: break;
0483: }
0484: case '}': {
0485: Position pos = matchClosingBrace(it.getPosition());
0486: pos = findBeginningOfStatement(pos);
0487: return buffer.getIndentation(pos.getLine());
0488: }
0489: case '{': {
0490: Line model = it.getLine();
0491: String modelText = trimSyntacticWhitespace(model
0492: .getText());
0493: if (modelText.equals("{"))
0494: return buffer.getIndentation(model)
0495: + buffer.getIndentSize();
0496: return indentAfterOpeningBrace(model, modelText, buffer);
0497: }
0498: case ':': {
0499: String firstIdentifier = getFirstIdentifier(it
0500: .getLine());
0501: if (firstIdentifier.equals("case")
0502: || firstIdentifier.equals("default"))
0503: return buffer.getIndentation(it.getLine())
0504: + buffer.getIndentSize();
0505: break;
0506: }
0507: case SyntaxIterator.DONE:
0508: return 0;
0509: }
0510: }
0511: }
0512:
0513: private boolean isInArrayInitializer(Line line) {
0514: // Find matching opening brace.
0515: Position match = matchClosingBrace(new Position(line, 0));
0516: SyntaxIterator it = getSyntaxIterator(match);
0517: char c;
0518: do {
0519: c = it.prevChar();
0520: } while (c != SyntaxIterator.DONE && Character.isWhitespace(c));
0521: if (c == '=' || c == ']')
0522: return true;
0523: return false;
0524: }
0525:
0526: protected static Line findModel(Line line) {
0527: Line model = line.previous();
0528: if (line.flags() == STATE_COMMENT) {
0529: // Any non-blank line is an acceptable model.
0530: while (model != null && model.isBlank())
0531: model = model.previous();
0532: } else {
0533: while (model != null) {
0534: if (isAcceptableModel(model))
0535: break; // Found an acceptable model.
0536: else
0537: model = model.previous();
0538: }
0539: }
0540: return model;
0541: }
0542:
0543: private static final boolean isAcceptableModel(Line line) {
0544: int flags = line.flags();
0545: if (flags == STATE_COMMENT || flags == STATE_QUOTE)
0546: return false;
0547: if (line.isBlank())
0548: return false;
0549: String trim = line.trim();
0550: char firstChar = trim.charAt(0);
0551: if (firstChar == '/') {
0552: if (trim.length() > 1 && trim.charAt(1) == '/')
0553: return false;
0554: } else if (firstChar == '#')
0555: return false;
0556: String s = trimSyntacticWhitespace(line.getText());
0557: if (s.length() == 0)
0558: return false;
0559: return true;
0560: }
0561:
0562: // Returns true if line contains opening brace of class or method.
0563: private boolean isOpeningBraceOfClassOrMethod(Line line) {
0564: if (line.length() == 0)
0565: return false;
0566: String text = trimSyntacticWhitespace(line.getText());
0567: if (!text.endsWith("{"))
0568: return false;
0569: if (text.equals("{")) {
0570: Line modelLine = findModel(line);
0571: if (modelLine == null)
0572: return true;
0573: Position beginningOfStatement = findBeginningOfStatement(new Position(
0574: modelLine, 0));
0575: text = beginningOfStatement.getLine().trim();
0576: } else
0577: text = text.substring(0, text.length() - 1).trim();
0578: if (text.indexOf('=') >= 0)
0579: return false;
0580: final String firstIdentifier = getFirstIdentifier(text);
0581: if (Utilities.isOneOf(firstIdentifier, conditionals))
0582: return false;
0583: if (firstIdentifier.equals("case")
0584: || firstIdentifier.equals("default"))
0585: return false;
0586: return true;
0587: }
0588:
0589: protected String getFirstIdentifier(String s) {
0590: return Utilities.getFirstIdentifier(s, this );
0591: }
0592:
0593: protected final String getFirstIdentifier(Line line) {
0594: return getFirstIdentifier(line.trim());
0595: }
0596:
0597: protected final String getLastIdentifier(String s) {
0598: int i = s.length() - 1;
0599: while (i >= 0) {
0600: if (isIdentifierPart(s.charAt(i))) {
0601: if (i > 0)
0602: --i;
0603: else
0604: break;
0605: } else {
0606: ++i;
0607: break;
0608: }
0609: }
0610: if (i >= 0 && i < s.length())
0611: return s.substring(i);
0612: return null;
0613: }
0614:
0615: private final String getIdentifierBefore(Position pos) {
0616: while (pos.prev())
0617: if (!Character.isWhitespace(pos.getChar()))
0618: break;
0619: while (isIdentifierPart(pos.getChar()) && pos.prev())
0620: ;
0621: while (!isIdentifierStart(pos.getChar()) && pos.next())
0622: ;
0623: return pos.getIdentifier(this );
0624: }
0625:
0626: protected final Position matchClosingBrace(Position start) {
0627: SyntaxIterator it = getSyntaxIterator(start);
0628: int count = 1;
0629: while (true) {
0630: switch (it.prevChar()) {
0631: case '}':
0632: ++count;
0633: break;
0634: case '{':
0635: --count;
0636: if (count == 0)
0637: return it.getPosition();
0638: break;
0639: case SyntaxIterator.DONE:
0640: return it.getPosition();
0641: default:
0642: break;
0643: }
0644: }
0645: }
0646:
0647: protected final Position matchClosingParen(Position start) {
0648: SyntaxIterator it = getSyntaxIterator(start);
0649: int count = 1;
0650: while (true) {
0651: switch (it.prevChar()) {
0652: case ')':
0653: ++count;
0654: break;
0655: case '(':
0656: --count;
0657: if (count == 0)
0658: return it.getPosition();
0659: break;
0660: case SyntaxIterator.DONE:
0661: return it.getPosition();
0662: default:
0663: break;
0664: }
0665: }
0666: }
0667:
0668: // Scan backwards from starting position, looking for unmatched opening
0669: // parenthesis.
0670: protected Position findEnclosingParen(Position start) {
0671: SyntaxIterator it = getSyntaxIterator(start);
0672: int parenCount = 0;
0673: int braceCount = 0;
0674: boolean seenBrace = false;
0675: while (true) {
0676: switch (it.prevChar()) {
0677: case '{':
0678: if (braceCount == 0)
0679: return null; // Found unmatched '{'.
0680: --braceCount;
0681: seenBrace = true;
0682: break;
0683: case '}':
0684: ++braceCount;
0685: seenBrace = true;
0686: break;
0687: case ';':
0688: if (seenBrace)
0689: return null;
0690: break;
0691: case ')':
0692: ++parenCount;
0693: break;
0694: case '(':
0695: if (parenCount == 0)
0696: return it.getPosition(); // Found unmatched '('.
0697: --parenCount;
0698: break;
0699: case SyntaxIterator.DONE:
0700: return null;
0701: default:
0702: break;
0703: }
0704: }
0705: }
0706:
0707: private final Position findEnclosingBrace(Position start) {
0708: SyntaxIterator it = getSyntaxIterator(start);
0709: int count = 0;
0710: while (true) {
0711: switch (it.prevChar()) {
0712: case '}':
0713: ++count;
0714: break;
0715: case '{':
0716: if (count == 0)
0717: return it.getPosition(); // Found unmatched '{'.
0718: --count;
0719: break;
0720: case SyntaxIterator.DONE:
0721: return null;
0722: default:
0723: break;
0724: }
0725: }
0726: }
0727:
0728: // Scan backwards from line, looking for the start of a switch statement.
0729: protected final Line findSwitch(Line line) {
0730: Position pos = findEnclosingBrace(new Position(line, 0));
0731: if (pos != null) {
0732: line = pos.getLine();
0733: do {
0734: String s = getFirstIdentifier(line);
0735: if (s.equals("switch"))
0736: return line;
0737: } while ((line = line.previous()) != null);
0738: }
0739: return null;
0740: }
0741:
0742: private Position matchElse(Position start) {
0743: SyntaxIterator it = getSyntaxIterator(start);
0744: int count = 1;
0745: char c;
0746: while ((c = it.prevChar()) != SyntaxIterator.DONE) {
0747: if (c == '}') {
0748: Position match = matchClosingBrace(it.getPosition());
0749: it = getSyntaxIterator(match);
0750: continue;
0751: }
0752: if (c == 'e') {
0753: Position pos = it.getPosition();
0754: if (pos.getIdentifier(this ).equals("else")) {
0755: ++count;
0756: continue;
0757: }
0758: }
0759: if (c == 'i') {
0760: Position pos = it.getPosition();
0761: if (pos.getIdentifier(this ).equals("if")) {
0762: --count;
0763: if (count == 0)
0764: return pos;
0765: continue;
0766: }
0767: }
0768: }
0769: return null;
0770: }
0771:
0772: public Position findBeginningOfStatement(Position start) {
0773: Position pos = new Position(start);
0774:
0775: final Position posParen = findEnclosingParen(pos);
0776:
0777: if (posParen != null)
0778: pos = posParen;
0779:
0780: final String trim = trimSyntacticWhitespace(pos.getLine()
0781: .getText());
0782: final String lastIdentifier = getLastIdentifier(trim);
0783: if (lastIdentifier != null && lastIdentifier.equals("else"))
0784: return new Position(pos.getLine(), 0); // BUG!! This is clearly wrong!
0785: final String firstIdentifier = getFirstIdentifier(trim);
0786: if (firstIdentifier != null
0787: && (firstIdentifier.equals("case") || firstIdentifier
0788: .equals("default")))
0789: return new Position(pos.getLine(), 0);
0790:
0791: while (pos.getLine().trim().startsWith("}")
0792: && pos.getPreviousLine() != null) {
0793: pos.moveTo(pos.getPreviousLine(), pos.getPreviousLine()
0794: .length());
0795: pos = matchClosingBrace(pos);
0796: }
0797:
0798: SyntaxIterator it = getSyntaxIterator(pos);
0799: boolean inParen = false;
0800: int count = 0;
0801: while (true) {
0802: char c = it.prevChar();
0803: if (c == SyntaxIterator.DONE)
0804: return it.getPosition();
0805: if (inParen) {
0806: if (c == ')')
0807: ++count;
0808: else if (c == '(') {
0809: --count;
0810: if (count == 0) // Found it!
0811: inParen = false;
0812: }
0813: continue;
0814: }
0815: if (c == ')') {
0816: inParen = true;
0817: count = 1;
0818: continue;
0819: }
0820: if (c == '{') {
0821: // If previous non-whitespace char is '=' then this is an array
0822: // initializer.
0823: pos = it.getPosition(); // Save position.
0824: char ch;
0825: do {
0826: ch = it.prevChar();
0827: } while (ch != SyntaxIterator.DONE
0828: && Character.isWhitespace(ch));
0829: if (ch == '=' || ch == ']') {
0830: // It is an array initializer.
0831: pos = it.getPosition();
0832: pos.moveTo(pos.getLine(), 0);
0833: return pos;
0834: }
0835: // Not an array initializer.
0836: it = getSyntaxIterator(pos); // Restore position.
0837: // Fall through...
0838: }
0839: if (";{}:".indexOf(c) >= 0) {
0840: do {
0841: c = it.nextChar();
0842: } while (c != SyntaxIterator.DONE
0843: && Character.isWhitespace(c));
0844: pos = it.getPosition();
0845: pos.setOffset(0);
0846: return pos;
0847: }
0848: }
0849: }
0850:
0851: public Position findPreviousConditional(Position start) {
0852: Position pos = start.copy();
0853: Position posParen = findEnclosingParen(pos);
0854: if (posParen != null)
0855: pos = posParen;
0856: while (pos.getLine().trim().startsWith("}")
0857: && pos.getPreviousLine() != null) {
0858: pos.moveTo(pos.getPreviousLine(), pos.getPreviousLine()
0859: .length());
0860: pos = matchClosingBrace(pos);
0861: }
0862: while (true) {
0863: if (pos.getLine().flags() != STATE_COMMENT) {
0864: String text = pos.getLine().trim();
0865: // Handle "} else".
0866: if (text.startsWith("}"))
0867: text = text.substring(1).trim();
0868: String firstIdentifier = getFirstIdentifier(text);
0869: if (Utilities.isOneOf(firstIdentifier, conditionals)) {
0870: pos.setOffset(pos.getLine().getText().indexOf(
0871: firstIdentifier));
0872: return pos;
0873: }
0874: }
0875: Line previousLine = pos.getPreviousLine();
0876: if (previousLine == null)
0877: return new Position(pos.getLine(), 0);
0878: pos.moveTo(previousLine, previousLine.length());
0879: if (pos.getLine().flags() == STATE_COMMENT)
0880: continue;
0881: posParen = findEnclosingParen(pos);
0882: if (posParen != null) {
0883: pos = posParen;
0884: continue;
0885: }
0886: String s = trimSyntacticWhitespace(pos.getLine().getText());
0887: if (s.length() > 0) {
0888: if (s.charAt(0) == '#') // C preprocessor.
0889: break;
0890: char lastChar = s.charAt(s.length() - 1);
0891: if (lastChar == ';' || lastChar == '{'
0892: || lastChar == '}' || lastChar == ':')
0893: break;
0894: }
0895: }
0896: // No conditional found.
0897: return start;
0898: }
0899:
0900: protected final boolean isContinued(String text, char lastChar) {
0901: switch (lastChar) {
0902: case '+':
0903: return !text.endsWith("++");
0904: case '/':
0905: return !text.endsWith("//");
0906: case '=':
0907: return (!text.endsWith("==") && !text.endsWith("!="));
0908: case ',':
0909: return true;
0910: case '.':
0911: return true;
0912: case '|':
0913: return text.endsWith("||");
0914: case '&':
0915: return text.endsWith("&&");
0916: default:
0917: return false;
0918: }
0919: }
0920:
0921: // Replaces syntactic whitespace (quotes and comments) with actual space
0922: // characters, then returns trimmed string.
0923: protected static String trimSyntacticWhitespace(String s) {
0924: JavaSyntaxIterator it = new JavaSyntaxIterator(null);
0925: return new String(it.hideSyntacticWhitespace(s)).trim();
0926: }
0927:
0928: public boolean isIdentifierStart(char c) {
0929: return Character.isJavaIdentifierStart(c);
0930: }
0931:
0932: public boolean isIdentifierPart(char c) {
0933: return Character.isJavaIdentifierPart(c);
0934: }
0935:
0936: public boolean isInComment(Buffer buffer, Position pos) {
0937: if (buffer == null || pos == null) {
0938: Debug.bug();
0939: return false;
0940: }
0941: final Line line = pos.getLine();
0942: final String text = line.getText();
0943: if (text == null)
0944: return false;
0945: final char[] chars = text.toCharArray();
0946: final int offset = pos.getOffset();
0947: if (buffer.needsParsing())
0948: buffer.getFormatter().parseBuffer();
0949: int state = line.flags();
0950: final int length = chars.length;
0951: for (int i = 0; i < length; i++) {
0952: if (i == offset)
0953: return state == STATE_COMMENT;
0954: char c = chars[i];
0955: if (c == '\\' && i < length - 1) {
0956: // Escape character.
0957: continue;
0958: }
0959: if (state == STATE_QUOTE) {
0960: if (c == '"')
0961: state = STATE_NEUTRAL;
0962: continue;
0963: }
0964: if (state == STATE_SINGLEQUOTE) {
0965: if (c == '\'')
0966: state = STATE_NEUTRAL;
0967: continue;
0968: }
0969: if (state == STATE_COMMENT) {
0970: if (c == '*' && i < length - 1 && chars[i + 1] == '/') {
0971: // /* */ comment ending
0972: state = STATE_NEUTRAL;
0973: }
0974: continue;
0975: }
0976: // Reaching here, STATE_NEUTRAL...
0977: if (c == '"') {
0978: state = STATE_QUOTE;
0979: continue;
0980: }
0981: if (c == '\'') {
0982: state = STATE_SINGLEQUOTE;
0983: continue;
0984: }
0985: if (c == '/') {
0986: if (i < length - 1) {
0987: if (chars[i + 1] == '*') {
0988: // /* */ comment starting
0989: state = STATE_COMMENT;
0990: continue;
0991: }
0992: if (chars[i + 1] == '/') {
0993: // "//" comment starting
0994: return true;
0995: }
0996: }
0997: }
0998: }
0999: return state == STATE_COMMENT;
1000: }
1001:
1002: public boolean isCommentLine(Line line) {
1003: return line.trim().startsWith("//");
1004: }
1005:
1006: public static void insertComment() {
1007: if (!Editor.checkExperimental())
1008: return;
1009: final Editor editor = Editor.currentEditor();
1010: String toBeInserted = Editor.preferences().getStringProperty(
1011: Property.JAVA_MODE_INSERT_COMMENT_TEXT);
1012: if (toBeInserted == null)
1013: toBeInserted = "/**\\n * |\\n */";
1014: Position caretPos = null;
1015: CompoundEdit compoundEdit = editor.beginCompoundEdit();
1016: final int limit = toBeInserted.length();
1017: for (int i = 0; i < limit; i++) {
1018: char c = toBeInserted.charAt(i);
1019: if (c == '|') {
1020: caretPos = new Position(editor.getDot());
1021: continue;
1022: }
1023: if (c == '\\' && i < limit - 1) {
1024: c = toBeInserted.charAt(++i);
1025: if (c == 'n') {
1026: editor.newlineAndIndent();
1027: continue;
1028: }
1029: // Otherwise fall through...
1030: }
1031: editor.insertChar(c);
1032: }
1033: if (caretPos != null)
1034: editor.moveDotTo(caretPos);
1035: editor.moveCaretToDotCol();
1036: editor.endCompoundEdit(compoundEdit);
1037: editor.getFormatter().parseBuffer();
1038: }
1039:
1040: public static void newlineAndIndentForComment() {
1041: final Editor editor = Editor.currentEditor();
1042: if (!editor.checkReadOnly())
1043: return;
1044: final Buffer buffer = editor.getBuffer();
1045: final Display display = editor.getDisplay();
1046: String commentPrefix = null;
1047: String s = editor.getDotLine().getText().trim();
1048: int flags = editor.getDotLine().flags();
1049: if (flags == STATE_COMMENT) {
1050: if (s.startsWith("*") && !s.endsWith("*/"))
1051: commentPrefix = "* ";
1052: } else {
1053: // Look for start of comment on current line.
1054: if (s.startsWith("/*") && !s.endsWith("*/"))
1055: commentPrefix = "* ";
1056: else if (s.startsWith("//"))
1057: commentPrefix = "// ";
1058: }
1059: if (commentPrefix == null) {
1060: // No special handling necessary.
1061: editor.newlineAndIndent();
1062: return;
1063: }
1064: CompoundEdit compoundEdit = buffer.beginCompoundEdit();
1065: if (editor.getMark() != null)
1066: editor.deleteRegion();
1067: editor.addUndo(SimpleEdit.INSERT_LINE_SEP);
1068: editor.insertLineSeparator();
1069: // Trim leading whitespace. (This code actually trims trailing
1070: // whitespace too.)
1071: editor.addUndo(SimpleEdit.LINE_EDIT);
1072: editor.getDotLine().setText(
1073: editor.getDotLine().getText().trim());
1074: // Insert the comment prefix at the start of the new line.
1075: editor.addUndo(SimpleEdit.INSERT_STRING);
1076: editor.insertStringInternal(commentPrefix);
1077: // Make the indentation code think we're still in a multi-line
1078: // comment, if we were in one before.
1079: editor.getDotLine().setFlags(flags);
1080: editor.indentLine();
1081: // We want dot to end up right after the comment prefix.
1082: editor.moveDotToIndentation();
1083: editor.getDot().skip(commentPrefix.length());
1084: display.moveCaretToDotCol();
1085: buffer.endCompoundEdit(compoundEdit);
1086: }
1087:
1088: public String getToolTipText(Editor editor, MouseEvent e) {
1089: if (editor.getModeId() == JAVA_MODE) {
1090: if (editor.getBuffer().getBooleanProperty(
1091: Property.ENABLE_TOOL_TIPS)) {
1092: Position pos = editor.getDisplay().positionFromPoint(
1093: e.getPoint());
1094: if (pos != null) {
1095: final String name = getQualifiedName(pos);
1096: if (name != null) {
1097: JavaContext context = new JavaContext(editor);
1098: context.parseContext(pos);
1099: JavaVariable var = context
1100: .findDeclaration(name);
1101: if (var != null)
1102: return var.toString();
1103: }
1104: }
1105: }
1106: }
1107: return null;
1108: }
1109:
1110: private String getQualifiedName(Position pos) {
1111: Line line = pos.getLine();
1112: int offset = pos.getOffset();
1113: final int limit = line.length();
1114: if (offset < limit) {
1115: char c = line.charAt(offset);
1116: if (isIdentifierPart(c)) {
1117: while (offset > 0) {
1118: --offset;
1119: c = line.charAt(offset);
1120: if (!isIdentifierPart(c) && c != '.') {
1121: ++offset;
1122: break;
1123: }
1124: }
1125: // Now we're looking at the first character of the identifier.
1126: c = line.charAt(offset);
1127: if (isIdentifierStart(c)) {
1128: FastStringBuffer sb = new FastStringBuffer();
1129: sb.append(c);
1130: while (++offset < limit) {
1131: c = line.charAt(offset);
1132: if (isIdentifierPart(c)) {
1133: sb.append(c);
1134: } else if (c == '.' && offset < pos.getOffset()) {
1135: // We don't want to go beyond the end of the
1136: // simple name at pos.
1137: sb.append(c);
1138: } else
1139: break;
1140: }
1141: return sb.toString();
1142: }
1143: }
1144: }
1145: return null;
1146: }
1147:
1148: public Expression getExpressionAtDot(final Editor editor,
1149: final boolean exact) {
1150: if (editor.getModeId() == OBJC_MODE)
1151: return super .getExpressionAtDot(editor, exact);
1152: if (editor.getDot() == null)
1153: return null;
1154: Position begin;
1155: if (editor.getMark() != null) {
1156: // Start at beginning of marked block.
1157: Region r = new Region(editor);
1158: begin = r.getBegin();
1159: } else
1160: begin = editor.getDot();
1161: final Line line = begin.getLine();
1162: final int offset = begin.getOffset();
1163: Position posExpr = null;
1164: if (exact) {
1165: if (offset < line.length()
1166: && isIdentifierPart(line.charAt(offset)))
1167: posExpr = findIdentifierStart(line, offset);
1168: if (posExpr == null)
1169: return null;
1170: }
1171: if (posExpr == null) {
1172: // Not exact, or no identifier there. Try to be smart.
1173: RE re = new UncheckedRE(
1174: "([A-Za-z_$]+[A-Za-z_$0-9]*)\\s*\\(");
1175: final String text = editor.getDotLine().getText();
1176: int index = 0;
1177: REMatch match;
1178: while ((match = re.getMatch(text, index)) != null) {
1179: String identifier = match.toString(1);
1180: if (!isKeyword(identifier)) {
1181: posExpr = new Position(line, match.getStartIndex());
1182: // If we've found a match to the right of dot, we're done.
1183: if (match.getEndIndex() > offset)
1184: break;
1185: }
1186: index = match.getEndIndex();
1187: }
1188: }
1189: if (posExpr == null) {
1190: // Smart didn't help. Go back to exact.
1191: if (offset < line.length()
1192: && isIdentifierStart(line.charAt(offset)))
1193: posExpr = findIdentifierStart(line, offset);
1194: }
1195: if (posExpr == null)
1196: return null;
1197: Position pos = posExpr.copy();
1198: // Gather up method name.
1199: FastStringBuffer sb = new FastStringBuffer();
1200: while (true) {
1201: char c = pos.getChar();
1202: if (!isIdentifierPart(c))
1203: break;
1204: sb.append(c);
1205: if (!pos.next())
1206: break;
1207: }
1208: String name = sb.toString().trim();
1209: // Skip whitespace (if any) between identifier and '('.
1210: while (true) {
1211: char c = pos.getChar();
1212: if (!Character.isWhitespace(c))
1213: break;
1214: if (!pos.next())
1215: break;
1216: }
1217: final int arity;
1218: if (editor.getModeId() == JAVASCRIPT_MODE)
1219: arity = -1; // Can't trust arity in JavaScript.
1220: else
1221: arity = getArity(editor, pos);
1222: if (arity >= 0)
1223: return new JavaExpression(name, arity);
1224: else
1225: return new JavaExpression(name, arity, TAG_UNKNOWN);
1226: }
1227:
1228: private int getArity(Editor editor, Position pos) {
1229: if (pos.getChar() != '(')
1230: return -1;
1231: if (!pos.next())
1232: return -1;
1233: final Position start = pos.copy();
1234: int parenCount = 0;
1235: int arity = 0;
1236: char quoteChar = '\0';
1237: boolean inQuote = false;
1238: while (!pos.atEnd()) {
1239: char c = pos.getChar();
1240: if (inQuote) {
1241: if (c == quoteChar)
1242: inQuote = false;
1243: pos.next();
1244: continue;
1245: }
1246: // Not in a quoted string.
1247: if (c == '"' || c == '\'') {
1248: inQuote = true;
1249: quoteChar = c;
1250: pos.next();
1251: continue;
1252: }
1253: if (c == ',') {
1254: if (parenCount == 0) // Top level.
1255: ++arity;
1256: pos.next();
1257: continue;
1258: }
1259: if (c == '(') {
1260: ++parenCount;
1261: pos.next();
1262: continue;
1263: }
1264: if (c == ')') {
1265: --parenCount;
1266: if (parenCount < 0) {
1267: // Closing paren, done.
1268: if (arity == 0) {
1269: // We haven't seen a comma.
1270: Region r = new Region(editor.getBuffer(),
1271: start, pos);
1272: if (r.toString().trim().length() > 0)
1273: arity = 1;
1274: } else
1275: ++arity;
1276: return arity;
1277: }
1278: pos.next();
1279: continue;
1280: }
1281: pos.next();
1282: }
1283: return -1;
1284: }
1285: }
|