Changeset 10993


Ignore:
Timestamp:
02/18/06 16:56:13 (15 years ago)
Author:
piso
Message:

Reformatted source.

File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/j/src/org/armedbear/j/JavaMode.java

    r10974 r10993  
    33 *
    44 * Copyright (C) 1998-2006 Peter Graves
    5  * $Id: JavaMode.java,v 1.19 2006-01-31 10:05:35 piso Exp $
     5 * $Id: JavaMode.java,v 1.20 2006-02-18 16:56:13 piso Exp $
    66 *
    77 * This program is free software; you can redistribute it and/or
     
    3333public class JavaMode extends AbstractMode implements Constants, Mode
    3434{
    35     private static final String[] javaConditionals = {
    36         "if",
    37         "else",
    38         "do",
    39         "while",
    40         "for",
    41         "switch",
    42         "try",
    43         "catch",
    44         "finally",
    45         "synchronized"
     35  private static final String[] javaConditionals =
     36    {
     37      "if",
     38      "else",
     39      "do",
     40      "while",
     41      "for",
     42      "switch",
     43      "try",
     44      "catch",
     45      "finally",
     46      "synchronized"
    4647    };
    4748
    48     private static Mode mode;
    49     private static Object jdb;
    50 
    51     protected String[] conditionals;
    52 
    53     private JavaMode()
    54     {
    55         super(JAVA_MODE, JAVA_MODE_NAME);
    56         keywords = new Keywords(this);
    57         conditionals = javaConditionals;
    58     }
    59 
    60     protected JavaMode(int id, String displayName)
    61     {
    62         super(id, displayName);
    63     }
    64 
    65     // Don't construct the singleton class instance until we actually need it,
    66     // to avoid unnecessary overhead for the derived classes.
    67     public static Mode getMode()
    68     {
    69         if (mode == null)
    70             mode = new JavaMode();
    71         return mode;
    72     }
    73 
    74     public static final Object getJdb()
    75     {
    76         return jdb;
    77     }
    78 
    79     public static final void setJdb(Object obj)
    80     {
    81         jdb = obj;
    82     }
    83 
    84     public boolean canIndent()
    85     {
     49  private static Mode mode;
     50  private static Object jdb;
     51
     52  protected String[] conditionals;
     53
     54  private JavaMode()
     55  {
     56    super(JAVA_MODE, JAVA_MODE_NAME);
     57    keywords = new Keywords(this);
     58    conditionals = javaConditionals;
     59  }
     60
     61  protected JavaMode(int id, String displayName)
     62  {
     63    super(id, displayName);
     64  }
     65
     66  // Don't construct the singleton class instance until we actually need it,
     67  // to avoid unnecessary overhead for the derived classes.
     68  public static Mode getMode()
     69  {
     70    if (mode == null)
     71      mode = new JavaMode();
     72    return mode;
     73  }
     74
     75  public static final Object getJdb()
     76  {
     77    return jdb;
     78  }
     79
     80  public static final void setJdb(Object obj)
     81  {
     82    jdb = obj;
     83  }
     84
     85  public boolean canIndent()
     86  {
     87    return true;
     88  }
     89
     90  public SyntaxIterator getSyntaxIterator(Position pos)
     91  {
     92    return new JavaSyntaxIterator(pos);
     93  }
     94
     95  public String getCommentStart()
     96  {
     97    return "// ";
     98  }
     99
     100  public Formatter getFormatter(Buffer buffer)
     101  {
     102    return new JavaFormatter(buffer);
     103  }
     104
     105  protected void setKeyMapDefaults(KeyMap km)
     106  {
     107    km.mapKey('{', "electricOpenBrace");
     108    km.mapKey('}', "electricCloseBrace");
     109    km.mapKey(KeyEvent.VK_TAB, CTRL_MASK, "insertTab");
     110    km.mapKey(KeyEvent.VK_TAB, 0, "tab");
     111    km.mapKey(KeyEvent.VK_ENTER, 0, "newlineAndIndent");
     112    km.mapKey(';', "electricSemi");
     113    km.mapKey(':', "electricColon");
     114    km.mapKey('*', "electricStar");
     115    km.mapKey(KeyEvent.VK_T, CTRL_MASK, "findTag");
     116    km.mapKey(KeyEvent.VK_PERIOD, ALT_MASK, "findTagAtDot");
     117    km.mapKey(KeyEvent.VK_COMMA, ALT_MASK, "listMatchingTagsAtDot");
     118    km.mapKey(KeyEvent.VK_PERIOD, CTRL_MASK | ALT_MASK, "findTagAtDotOtherWindow");
     119    km.mapKey(')', "closeParen");
     120    km.mapKey(KeyEvent.VK_I, ALT_MASK, "cycleIndentSize");
     121
     122    km.mapKey(KeyEvent.VK_9, CTRL_MASK | SHIFT_MASK, "insertParentheses");
     123    km.mapKey(KeyEvent.VK_0, CTRL_MASK | SHIFT_MASK, "movePastCloseAndReindent");
     124
     125    km.mapKey(KeyEvent.VK_OPEN_BRACKET, CTRL_MASK | SHIFT_MASK, "insertBraces");
     126    // Duplicate mapping for 1.4.
     127    km.mapKey(KeyEvent.VK_BRACELEFT, CTRL_MASK | SHIFT_MASK, "insertBraces");
     128
     129    km.mapKey(KeyEvent.VK_F12, 0, "wrapComment");
     130
     131    // Duplicate mapping to support IBM 1.3 for Linux.
     132    km.mapKey(0xffc9, 0, "wrapComment"); // F12
     133
     134    km.mapKey(KeyEvent.VK_OPEN_BRACKET, CTRL_MASK, "fold");
     135    km.mapKey(KeyEvent.VK_CLOSE_BRACKET, CTRL_MASK, "unfold");
     136
     137    km.mapKey(KeyEvent.VK_F9, 0, "compile");
     138    km.mapKey(KeyEvent.VK_F9, CTRL_MASK, "recompile");
     139    km.mapKey(KeyEvent.VK_F1, ALT_MASK, "jdkHelp");
     140    km.mapKey(KeyEvent.VK_F1, CTRL_MASK, "source");
     141
     142    // This is the "normal" mapping.
     143    km.mapKey(KeyEvent.VK_COMMA,  CTRL_MASK | SHIFT_MASK, "htmlInsertTag");
     144    // The "normal" mapping doesn't work for Linux, but this does.
     145    km.mapKey(0x7c, CTRL_MASK | SHIFT_MASK, "htmlInsertTag");
     146
     147    if (Editor.checkExperimental())
     148      {
     149        km.mapKey(KeyEvent.VK_SEMICOLON, ALT_MASK, "JavaMode.insertComment");
     150        km.mapKey(KeyEvent.VK_ENTER, ALT_MASK, "JavaMode.newlineAndIndentForComment");
     151      }
     152
     153    if (Platform.isPlatformLinux())
     154      {
     155        // Blackdown 1.1.7v3, 1.2pre2, IBM 1.1.8.
     156        // Duplicate mappings needed for VK_9, VK_0 and VK_OPEN_BRACKET.
     157        km.mapKey(0x68, CTRL_MASK | SHIFT_MASK, "insertParentheses");
     158        km.mapKey(0x69, CTRL_MASK | SHIFT_MASK, "movePastCloseAndReindent");
     159        km.mapKey(0xbb, CTRL_MASK | SHIFT_MASK, "insertBraces");
     160      }
     161  }
     162
     163  public void populateModeMenu(Editor editor, Menu menu)
     164  {
     165    menu.add(editor, "Compile...", 'C', "compile");
     166    menu.add(editor, "Recompile", 'R', "recompile");
     167    boolean enabled = CompilationCommands.getCompilationBuffer() != null;
     168    menu.addSeparator();
     169    menu.add(editor, "Next Error", 'N', "nextError", enabled);
     170    menu.add(editor, "Previous Error", 'P', "previousError", enabled);
     171    menu.add(editor, "Show Error Message", 'M', "showMessage", enabled);
     172    menu.addSeparator();
     173    MenuItem jdbMenuItem = menu.add(editor, "Debug...", 'D', "jdb");
     174    if (jdb != null)
     175      jdbMenuItem.setEnabled(false);
     176    else
     177      {
     178        try
     179          {
     180            Class.forName("com.sun.jdi.Bootstrap");
     181          }
     182        catch (ClassNotFoundException e)
     183          {
     184            jdbMenuItem.setEnabled(false);
     185          }
     186      }
     187  }
     188
     189  public JPopupMenu getContextMenu(Editor editor)
     190  {
     191    final JPopupMenu popup = new JPopupMenu();
     192    if (jdb != null)
     193      {
     194        final Line line = editor.getDotLine();
     195        if (line != null)
     196          {
     197            final Dispatcher dispatcher = editor.getDispatcher();
     198            JMenuItem menuItem =
     199              addContextMenuItem("Set breakpoint",
     200                                 "jdbSetBreakpoint", popup, dispatcher);
     201            if (line.isBlank() || line.getAnnotation() != null)
     202              menuItem.setEnabled(false);
     203            menuItem =
     204              addContextMenuItem("Delete breakpoint",
     205                                 "jdbDeleteBreakpoint", popup, dispatcher);
     206            if (line.getAnnotation() == null)
     207              menuItem.setEnabled(false);
     208            menuItem =
     209              addContextMenuItem("Run to current line",
     210                                 "jdbRunToCurrentLine", popup, dispatcher);
     211            if (line.isBlank())
     212              menuItem.setEnabled(false);
     213            popup.addSeparator();
     214          }
     215      }
     216    addDefaultContextMenuItems(editor, popup);
     217    popup.pack();
     218    return popup;
     219  }
     220
     221  public NavigationComponent getSidebarComponent(Editor editor)
     222  {
     223    if (getId() == JAVA_MODE)
     224      {
     225        View view = editor.getCurrentView();
     226        if (view == null)
     227          return null; // Shouldn't happen.
     228        if (view.getSidebarComponent() == null)
     229          view.setSidebarComponent(new JavaTree(editor));
     230        return view.getSidebarComponent();
     231      }
     232    // For subclasses...
     233    return super.getSidebarComponent(editor);
     234  }
     235
     236  public Tagger getTagger(SystemBuffer buffer)
     237  {
     238    return new JavaTagger(buffer);
     239  }
     240
     241  public boolean isTaggable()
     242  {
     243    return true;
     244  }
     245
     246  public boolean hasQualifiedNames()
     247  {
     248    return true;
     249  }
     250
     251  public boolean isQualifiedName(String s)
     252  {
     253    return s.indexOf('.') >= 0;
     254  }
     255
     256  public int getCorrectIndentation(final Line line, final Buffer buffer)
     257  {
     258    if (line.flags() == STATE_COMMENT)
     259      return indentComment(line, buffer);
     260    final String text = line.trim();
     261    final char textFirstChar = text.length() > 0 ? text.charAt(0) : 0;
     262    if (textFirstChar == '}')
     263      return indentClosingBrace(line, buffer);
     264
     265    if (textFirstChar == 'c' || textFirstChar == 'd')
     266      {
     267        // Does line begin with "case" or "default"?
     268        final String firstIdentifier = getFirstIdentifier(text);
     269        if (firstIdentifier.equals("case") || firstIdentifier.equals("default"))
     270          return indentSwitchLabel(line, buffer);
     271        // Otherwise fall through...
     272      }
     273    else if (textFirstChar == 'e')
     274      {
     275        // Does line begin with "else" or "elseif"?
     276        final String firstIdentifier = getFirstIdentifier(text);
     277        if (firstIdentifier.equals("else") || firstIdentifier.equals("elseif"))
     278          {
     279            Position match = matchElse(new Position(line, 0));
     280            if (match != null)
     281              return buffer.getIndentation(match.getLine());
     282          }
     283        // Otherwise fall through...
     284      }
     285    else if (textFirstChar == '"')
     286      {
     287        // If quoted text starts in column 0, leave it alone.
     288        if (line.charAt(0) == '"')
     289          return 0;
     290      }
     291
     292    Position paren = findEnclosingParen(new Position(line, 0));
     293    if (paren != null)
     294      return indentInParen(paren, buffer);
     295
     296    final Line model = findModel(line);
     297    if (model == null)
     298      return 0;
     299
     300    final int indentSize = buffer.getIndentSize();
     301
     302    final String firstIdentifier = getFirstIdentifier(text);
     303    if (firstIdentifier.equals("throws") ||
     304        firstIdentifier.equals("implements"))
     305      {
     306        Position pos = findBeginningOfStatement(new Position(model, 0));
     307        return buffer.getIndentation(pos.getLine()) + indentSize;
     308      }
     309
     310    final String modelText = trimSyntacticWhitespace(model.getText());
     311
     312    // Model line can't be blank, so this is safe.
     313    final char modelLastChar = modelText.charAt(modelText.length() - 1);
     314
     315    if (modelLastChar == '{')
     316      return indentAfterOpeningBrace(model, modelText, buffer);
     317
     318    if (modelLastChar == ')')
     319      return indentAfterCloseParen(model, text, textFirstChar, buffer);
     320
     321    final String lastIdentifier = getLastIdentifier(modelText);
     322    if (lastIdentifier != null && lastIdentifier.equals("else"))
     323      return indentAfterElse(model, text, textFirstChar, buffer);
     324
     325    final char modelFirstChar = modelText.charAt(0);
     326    if (modelFirstChar == 'c' || modelFirstChar == 'd')
     327      {
     328        final String modelFirstIdentifier = getFirstIdentifier(modelText);
     329        if (modelFirstIdentifier.equals("case") ||
     330            modelFirstIdentifier.equals("default"))
     331          return indentAfterSwitchLabel(model, text, textFirstChar, buffer);
     332        // Otherwise fall through...
     333      }
     334
     335    final int indent = getIndentationOfEnclosingScope(line, buffer);
     336
     337    if (textFirstChar == '{')
     338      {
     339        if (buffer.getBooleanProperty(Property.INDENT_BEFORE_BRACE))
     340          {
     341            // Never indent before the opening brace of a class or method.
     342            if (!isOpeningBraceOfClassOrMethod(line))
     343              return indent + indentSize;
     344          }
     345        return indent;
     346      }
     347
     348    if (textFirstChar == '=')
     349      return indent + indentSize;
     350
     351    if (modelLastChar == ',')
     352      {
     353        if (buffer.getModeId() == CPP_MODE && modelFirstChar == ':')
     354          {
     355            // Model line is start of member initialization list, current
     356            // line is continuation.
     357            return indent + 2;
     358          }
     359        if (isInArrayInitializer(line))
     360          return indent;
     361        // Otherwise it's a continuation line.
     362        return indent + indentSize;
     363      }
     364
     365    // Check for continuation line.
     366    if (isContinued(modelText, modelLastChar))
     367      return indent + indentSize;
     368
     369    return indent;
     370  }
     371
     372  private final int indentComment(Line line, Buffer buffer)
     373  {
     374    final Line model = findModel(line);
     375    if (model == null)
     376      return 0;
     377    int indent = buffer.getIndentation(model);
     378    if (model.trim().startsWith("/*"))
     379      if (line.trim().startsWith("*"))
     380        return indent+1;
     381    return indent;
     382  }
     383
     384  protected int indentClosingBrace(Line line, Buffer buffer)
     385  {
     386    Position pos = matchClosingBrace(new Position(line, 0));
     387    if (isOpeningBraceOfClassOrMethod(pos.getLine()))
     388      pos = findBeginningOfStatement(pos);
     389    else if (!pos.getLine().trim().startsWith("{"))
     390      pos = findPreviousConditional(pos);
     391    return buffer.getIndentation(pos.getLine());
     392  }
     393
     394  private final int indentSwitchLabel(Line line, Buffer buffer)
     395  {
     396    Line switchLine = findSwitch(line);
     397    if (switchLine != null)
     398      return buffer.getIndentation(switchLine) + buffer.getIndentSize();
     399    return 0;
     400  }
     401
     402  private final int indentInParen(Position posParen, Buffer buffer)
     403  {
     404    final Line line = posParen.getLine();
     405    if (line.trim().endsWith("(") || !buffer.getBooleanProperty(Property.LINEUP_ARGLIST))
     406      return buffer.getIndentation(line) + buffer.getIndentSize();
     407    final int limit = line.length();
     408    int offset = posParen.getOffset();
     409    do
     410      {
     411        ++offset;
     412      } while (offset < limit && line.charAt(offset) <= ' ');
     413    if (offset <= limit)
     414      return buffer.getCol(line, offset);
     415    return 0;
     416  }
     417
     418  private final int indentAfterOpeningBrace(Line model, String modelText,
     419                                            Buffer buffer)
     420  {
     421    final int indentSize = buffer.getIndentSize();
     422    if (isOpeningBraceOfClassOrMethod(model))
     423      {
     424        Position pos = findBeginningOfStatement(new Position(model, 0));
     425        int indent = buffer.getIndentation(pos.getLine());
     426        if (buffer.getBooleanProperty(Property.INDENT_AFTER_OPENING_BRACE))
     427          indent += indentSize;
     428        return indent;
     429      }
     430    Position pos = new Position(model, model.length()-1);
     431    if (modelText.charAt(0) != '{')
     432      pos = findPreviousConditional(pos);
     433    int indent = buffer.getIndentation(pos.getLine());
     434    if (buffer.getBooleanProperty(Property.INDENT_AFTER_BRACE))
     435      indent += indentSize;
     436    final boolean indentBeforeBrace =
     437      buffer.getBooleanProperty(Property.INDENT_BEFORE_BRACE);
     438    if (indentBeforeBrace && pos.getLine() != model)
     439      indent += indentSize;
     440    return indent;
     441  }
     442
     443  private final int indentAfterElse(Line model, String text,
     444                                    char textFirstChar, Buffer buffer)
     445  {
     446    int indent = buffer.getIndentation(model);
     447    final boolean indentBeforeBrace =
     448      buffer.getBooleanProperty(Property.INDENT_BEFORE_BRACE);
     449    if (indentBeforeBrace || textFirstChar != '{')
     450      return indent + buffer.getIndentSize();
     451    else
     452      return indent;
     453  }
     454
     455  private final int indentAfterSwitchLabel(Line model, String text,
     456                                           char textFirstChar, Buffer buffer)
     457  {
     458    int indent = buffer.getIndentation(model);
     459    final boolean indentBeforeBrace =
     460      buffer.getBooleanProperty(Property.INDENT_BEFORE_BRACE);
     461    if (indentBeforeBrace || textFirstChar != '{')
     462      return indent + buffer.getIndentSize();
     463    else
     464      return indent;
     465  }
     466
     467  private final int indentAfterCloseParen(Line model, String text,
     468                                          char textFirstChar, Buffer buffer)
     469  {
     470    // Find matching '('.
     471    SyntaxIterator it = getSyntaxIterator(new Position(model, model.length()));
     472    char c;
     473    do
     474      {
     475        c = it.prevChar();
     476      } while (c != SyntaxIterator.DONE && c != ')');
     477    Position pos = it.getPosition();
     478    pos = matchClosingParen(pos);
     479    boolean indent = false;
     480    final String s = getIdentifierBefore(pos);
     481    final String[] indentAfter = {"if", "while", "for", "switch", "catch"};
     482    if (Utilities.isOneOf(s, indentAfter))
     483      {
     484        indent = true;
     485      }
     486    else if (buffer.getModeId() == JAVA_MODE)
     487      {
     488        if (s.equals("synchronized"))
     489          indent = true;
     490      }
     491    else if (buffer.getModeId() == PHP_MODE)
     492      {
     493        if (s.equals("elseif") || s.equals("foreach"))
     494          indent = true;
     495      }
     496    if (indent)
     497      {
     498        int modelIndent = buffer.getIndentation(pos.getLine());
     499        if (textFirstChar != '{' ||
     500            buffer.getBooleanProperty(Property.INDENT_BEFORE_BRACE))
     501          return modelIndent + buffer.getIndentSize();
     502        else
     503          return modelIndent;
     504      }
     505    if (buffer.getModeId() == JAVA_MODE)
     506      {
     507        RE re = new UncheckedRE("\\s+new\\s+");
     508        if (re.getMatch(pos.getLine().getText().substring(0, pos.getOffset())) != null)
     509          indent = true;
     510      }
     511    int modelIndent =
     512      buffer.getIndentation(findBeginningOfStatement(pos).getLine());
     513    if (indent && (textFirstChar != '{' || buffer.getBooleanProperty(Property.INDENT_BEFORE_BRACE)))
     514      return modelIndent + buffer.getIndentSize();
     515    else
     516      return modelIndent;
     517  }
     518
     519  private final int getIndentationOfEnclosingScope(Line line, Buffer buffer)
     520  {
     521    SyntaxIterator it = getSyntaxIterator(new Position(line, 0));
     522    loop:
     523    while (true)
     524      {
     525        switch (it.prevChar())
     526          {
     527          case ')':
     528            {
     529              Position pos = matchClosingParen(it.getPosition());
     530              it = getSyntaxIterator(pos);
     531              break;
     532            }
     533          case '}':
     534            {
     535              Position pos = matchClosingBrace(it.getPosition());
     536              if (pos.getOffset() == 0)
     537                return 0;
     538              pos = findBeginningOfStatement(pos);
     539              return buffer.getIndentation(pos.getLine());
     540            }
     541          case '{':
     542            {
     543              Line model = it.getLine();
     544              String modelText = trimSyntacticWhitespace(model.getText());
     545              if (modelText.equals("{"))
     546                return buffer.getIndentation(model) + buffer.getIndentSize();
     547              return indentAfterOpeningBrace(model, modelText, buffer);
     548            }
     549          case ':':
     550            {
     551              String firstIdentifier = getFirstIdentifier(it.getLine());
     552              if (firstIdentifier.equals("case") || firstIdentifier.equals("default"))
     553                return buffer.getIndentation(it.getLine()) + buffer.getIndentSize();
     554              break;
     555            }
     556          case SyntaxIterator.DONE:
     557            return 0;
     558          }
     559      }
     560  }
     561
     562  private boolean isInArrayInitializer(Line line)
     563  {
     564    // Find matching opening brace.
     565    Position match = matchClosingBrace(new Position(line, 0));
     566    SyntaxIterator it = getSyntaxIterator(match);
     567    char c;
     568    do
     569      {
     570        c = it.prevChar();
     571      } while (c != SyntaxIterator.DONE && Character.isWhitespace(c));
     572    if (c == '=' || c == ']')
     573      return true;
     574    return false;
     575  }
     576
     577  protected static Line findModel(Line line)
     578  {
     579    Line model = line.previous();
     580    if (line.flags() == STATE_COMMENT)
     581      {
     582        // Any non-blank line is an acceptable model.
     583        while (model != null && model.isBlank())
     584          model = model.previous();
     585      }
     586    else
     587      {
     588        while (model != null)
     589          {
     590            if (isAcceptableModel(model))
     591              break; // Found an acceptable model.
     592            else
     593              model = model.previous();
     594          }
     595      }
     596    return model;
     597  }
     598
     599  private static final boolean isAcceptableModel(Line line)
     600  {
     601    int flags = line.flags();
     602    if (flags == STATE_COMMENT || flags == STATE_QUOTE)
     603      return false;
     604    if (line.isBlank())
     605      return false;
     606    String trim = line.trim();
     607    char firstChar = trim.charAt(0);
     608    if (firstChar == '/')
     609      {
     610        if (trim.length() > 1 && trim.charAt(1) =='/')
     611          return false;
     612      }
     613    else if (firstChar == '#')
     614      return false;
     615    else if (firstChar == '=')
     616      return false;
     617    else if (firstChar == ':')
     618      {
     619        if (trim.startsWith(": ") || trim.startsWith(":\t"))
     620          return false;
     621      }
     622    String s = trimSyntacticWhitespace(line.getText());
     623    if (s.length() == 0)
     624      return false;
     625    return true;
     626  }
     627
     628  // Returns true if line contains opening brace of class or method.
     629  private boolean isOpeningBraceOfClassOrMethod(Line line)
     630  {
     631    if (line.length() == 0)
     632      return false;
     633    String text = trimSyntacticWhitespace(line.getText());
     634    if (!text.endsWith("{"))
     635      return false;
     636    if (text.equals("{"))
     637      {
     638        Line modelLine = findModel(line);
     639        if (modelLine == null)
     640          return true;
     641        Position beginningOfStatement =
     642          findBeginningOfStatement(new Position(modelLine, 0));
     643        text = beginningOfStatement.getLine().trim();
     644      }
     645    else
     646      text = text.substring(0, text.length()-1).trim();
     647    if (text.indexOf('=') >= 0)
     648      return false;
     649    final String firstIdentifier = getFirstIdentifier(text);
     650    if (Utilities.isOneOf(firstIdentifier, conditionals))
     651      return false;
     652    if (firstIdentifier.equals("case") || firstIdentifier.equals("default"))
     653      return false;
     654    return true;
     655  }
     656
     657  protected String getFirstIdentifier(String s)
     658  {
     659    return Utilities.getFirstIdentifier(s, this);
     660  }
     661
     662  protected final String getFirstIdentifier(Line line)
     663  {
     664    return getFirstIdentifier(line.trim());
     665  }
     666
     667  protected final String getLastIdentifier(String s)
     668  {
     669    int i = s.length()-1;
     670    while (i >= 0)
     671      {
     672        if (isIdentifierPart(s.charAt(i)))
     673          {
     674            if (i > 0)
     675              --i;
     676            else
     677              break;
     678          }
     679        else
     680          {
     681            ++i;
     682            break;
     683          }
     684      }
     685    if (i >= 0 && i < s.length())
     686      return s.substring(i);
     687    return null;
     688  }
     689
     690  private final String getIdentifierBefore(Position pos)
     691  {
     692    while (pos.prev())
     693      if (!Character.isWhitespace(pos.getChar()))
     694        break;
     695    while (isIdentifierPart(pos.getChar()) && pos.prev())
     696      ;
     697    while (!isIdentifierStart(pos.getChar()) && pos.next())
     698      ;
     699    return pos.getIdentifier(this);
     700  }
     701
     702  protected final Position matchClosingBrace(Position start)
     703  {
     704    SyntaxIterator it = getSyntaxIterator(start);
     705    int count = 1;
     706    while (true)
     707      {
     708        switch (it.prevChar())
     709          {
     710          case '}':
     711            ++count;
     712            break;
     713          case '{':
     714            --count;
     715            if (count == 0)
     716              return it.getPosition();
     717            break;
     718          case SyntaxIterator.DONE:
     719            return it.getPosition();
     720          default:
     721            break;
     722          }
     723      }
     724  }
     725
     726  protected final Position matchClosingParen(Position start)
     727  {
     728    SyntaxIterator it = getSyntaxIterator(start);
     729    int count = 1;
     730    while (true)
     731      {
     732        switch (it.prevChar())
     733          {
     734          case ')':
     735            ++count;
     736            break;
     737          case '(':
     738            --count;
     739            if (count == 0)
     740              return it.getPosition();
     741            break;
     742          case SyntaxIterator.DONE:
     743            return it.getPosition();
     744          default:
     745            break;
     746          }
     747      }
     748  }
     749
     750  // Scan backwards from starting position, looking for unmatched opening
     751  // parenthesis.
     752  protected Position findEnclosingParen(Position start)
     753  {
     754    SyntaxIterator it = getSyntaxIterator(start);
     755    int parenCount = 0;
     756    int braceCount = 0;
     757    boolean seenBrace = false;
     758    while (true)
     759      {
     760        switch (it.prevChar())
     761          {
     762          case '{':
     763            if (braceCount == 0)
     764              return null; // Found unmatched '{'.
     765            --braceCount;
     766            seenBrace = true;
     767            break;
     768          case '}':
     769            ++braceCount;
     770            seenBrace = true;
     771            break;
     772          case ';':
     773            if (seenBrace)
     774              return null;
     775            break;
     776          case ')':
     777            ++parenCount;
     778            break;
     779          case '(':
     780            if (parenCount == 0)
     781              return it.getPosition(); // Found unmatched '('.
     782            --parenCount;
     783            break;
     784          case SyntaxIterator.DONE:
     785            return null;
     786          default:
     787            break;
     788          }
     789      }
     790  }
     791
     792  private final Position findEnclosingBrace(Position start)
     793  {
     794    SyntaxIterator it = getSyntaxIterator(start);
     795    int count = 0;
     796    while (true)
     797      {
     798        switch (it.prevChar())
     799          {
     800          case '}':
     801            ++count;
     802            break;
     803          case '{':
     804            if (count == 0)
     805              return it.getPosition(); // Found unmatched '{'.
     806            --count;
     807            break;
     808          case SyntaxIterator.DONE:
     809            return null;
     810          default:
     811            break;
     812          }
     813      }
     814  }
     815
     816  // Scan backwards from line, looking for the start of a switch statement.
     817  protected final Line findSwitch(Line line)
     818  {
     819    Position pos = findEnclosingBrace(new Position(line, 0));
     820    if (pos != null)
     821      {
     822        line = pos.getLine();
     823        do
     824          {
     825            String s = getFirstIdentifier(line);
     826            if (s.equals("switch"))
     827              return line;
     828          } while ((line = line.previous()) != null);
     829      }
     830    return null;
     831  }
     832
     833  private Position matchElse(Position start)
     834  {
     835    SyntaxIterator it = getSyntaxIterator(start);
     836    int count = 1;
     837    char c;
     838    while ((c = it.prevChar()) != SyntaxIterator.DONE)
     839      {
     840        if (c == '}')
     841          {
     842            Position match = matchClosingBrace(it.getPosition());
     843            it = getSyntaxIterator(match);
     844            continue;
     845          }
     846        if (c == 'e')
     847          {
     848            Position pos = it.getPosition();
     849            if (pos.getIdentifier(this).equals("else"))
     850              {
     851                ++count;
     852                continue;
     853              }
     854          }
     855        if (c == 'i')
     856          {
     857            Position pos = it.getPosition();
     858            if (pos.getIdentifier(this).equals("if"))
     859              {
     860                --count;
     861                if (count == 0)
     862                  return pos;
     863                continue;
     864              }
     865          }
     866      }
     867    return null;
     868  }
     869
     870  public Position findBeginningOfStatement(Position start)
     871  {
     872    Position pos = new Position(start);
     873
     874    final Position posParen = findEnclosingParen(pos);
     875
     876    if (posParen != null)
     877      pos = posParen;
     878
     879    final String trim = trimSyntacticWhitespace(pos.getLine().getText());
     880    final String lastIdentifier = getLastIdentifier(trim);
     881    if (lastIdentifier != null && lastIdentifier.equals("else"))
     882      return new Position(pos.getLine(), 0); // BUG!! This is clearly wrong!
     883    final String firstIdentifier = getFirstIdentifier(trim);
     884    if (firstIdentifier != null &&
     885        (firstIdentifier.equals("case") || firstIdentifier.equals("default")))
     886      return new Position(pos.getLine(), 0);
     887
     888    while (pos.getLine().trim().startsWith("}") && pos.getPreviousLine() != null)
     889      {
     890        pos.moveTo(pos.getPreviousLine(), pos.getPreviousLine().length());
     891        pos = matchClosingBrace(pos);
     892      }
     893
     894    SyntaxIterator it = getSyntaxIterator(pos);
     895    boolean inParen = false;
     896    int count = 0;
     897    while (true)
     898      {
     899        char c = it.prevChar();
     900        if (c == SyntaxIterator.DONE)
     901          return it.getPosition();
     902        if (inParen)
     903          {
     904            if (c == ')')
     905              ++count;
     906            else if (c == '(')
     907              {
     908                --count;
     909                if (count == 0) // Found it!
     910                  inParen = false;
     911              }
     912            continue;
     913          }
     914        if (c == ')')
     915          {
     916            inParen = true;
     917            count = 1;
     918            continue;
     919          }
     920        if (c == '{')
     921          {
     922            // If previous non-whitespace char is '=' then this is an array
     923            // initializer.
     924            pos = it.getPosition(); // Save position.
     925            char ch;
     926            do
     927              {
     928                ch = it.prevChar();
     929              } while (ch != SyntaxIterator.DONE && Character.isWhitespace(ch));
     930            if (ch == '=' || ch == ']')
     931              {
     932                // It is an array initializer.
     933                pos = it.getPosition();
     934                pos.moveTo(pos.getLine(), 0);
     935                return pos;
     936              }
     937            // Not an array initializer.
     938            it = getSyntaxIterator(pos); // Restore position.
     939            // Fall through...
     940          }
     941        if (";{}:".indexOf(c) >= 0)
     942          {
     943            do
     944              {
     945                c = it.nextChar();
     946              } while (c != SyntaxIterator.DONE && Character.isWhitespace(c));
     947            pos = it.getPosition();
     948            pos.setOffset(0);
     949            return pos;
     950          }
     951      }
     952  }
     953
     954  public Position findPreviousConditional(Position start)
     955  {
     956    Position pos = start.copy();
     957    Position posParen = findEnclosingParen(pos);
     958    if (posParen != null)
     959      pos = posParen;
     960    while (pos.getLine().trim().startsWith("}") && pos.getPreviousLine() != null)
     961      {
     962        pos.moveTo(pos.getPreviousLine(), pos.getPreviousLine().length());
     963        pos = matchClosingBrace(pos);
     964      }
     965    while (true)
     966      {
     967        if (pos.getLine().flags() != STATE_COMMENT)
     968          {
     969            String text = pos.getLine().trim();
     970            // Handle "} else".
     971            if (text.startsWith("}"))
     972              text = text.substring(1).trim();
     973            String firstIdentifier = getFirstIdentifier(text);
     974            if (Utilities.isOneOf(firstIdentifier, conditionals))
     975              {
     976                pos.setOffset(pos.getLine().getText().indexOf(firstIdentifier));
     977                return pos;
     978              }
     979          }
     980        Line previousLine = pos.getPreviousLine();
     981        if (previousLine == null)
     982          return new Position(pos.getLine(), 0);
     983        pos.moveTo(previousLine, previousLine.length());
     984        if (pos.getLine().flags() == STATE_COMMENT)
     985          continue;
     986        posParen = findEnclosingParen(pos);
     987        if (posParen != null)
     988          {
     989            pos = posParen;
     990            continue;
     991          }
     992        String s = trimSyntacticWhitespace(pos.getLine().getText());
     993        if (s.length() > 0)
     994          {
     995            if (s.charAt(0) == '#') // C preprocessor.
     996              break;
     997            char lastChar = s.charAt(s.length()-1);
     998            if (lastChar == ';' || lastChar == '{' || lastChar == '}' ||
     999                lastChar == ':')
     1000              break;
     1001          }
     1002      }
     1003    // No conditional found.
     1004    return start;
     1005  }
     1006
     1007  protected final boolean isContinued(String text, char lastChar)
     1008  {
     1009    switch (lastChar)
     1010      {
     1011      case '+':
     1012        return !text.endsWith("++");
     1013      case '/':
     1014        return !text.endsWith("//");
     1015      case '=':
     1016        return (!text.endsWith("==") && !text.endsWith("!="));
     1017      case ',':
    861018        return true;
    87     }
    88 
    89     public SyntaxIterator getSyntaxIterator(Position pos)
    90     {
    91         return new JavaSyntaxIterator(pos);
    92     }
    93 
    94     public String getCommentStart()
    95     {
    96         return "// ";
    97     }
    98 
    99     public Formatter getFormatter(Buffer buffer)
    100     {
    101         return new JavaFormatter(buffer);
    102     }
    103 
    104     protected void setKeyMapDefaults(KeyMap km)
    105     {
    106         km.mapKey('{', "electricOpenBrace");
    107         km.mapKey('}', "electricCloseBrace");
    108         km.mapKey(KeyEvent.VK_TAB, CTRL_MASK, "insertTab");
    109         km.mapKey(KeyEvent.VK_TAB, 0, "tab");
    110         km.mapKey(KeyEvent.VK_ENTER, 0, "newlineAndIndent");
    111         km.mapKey(';', "electricSemi");
    112         km.mapKey(':', "electricColon");
    113         km.mapKey('*', "electricStar");
    114         km.mapKey(KeyEvent.VK_T, CTRL_MASK, "findTag");
    115         km.mapKey(KeyEvent.VK_PERIOD, ALT_MASK, "findTagAtDot");
    116         km.mapKey(KeyEvent.VK_COMMA, ALT_MASK, "listMatchingTagsAtDot");
    117         km.mapKey(KeyEvent.VK_PERIOD, CTRL_MASK | ALT_MASK, "findTagAtDotOtherWindow");
    118         km.mapKey(')', "closeParen");
    119         km.mapKey(KeyEvent.VK_I, ALT_MASK, "cycleIndentSize");
    120 
    121         km.mapKey(KeyEvent.VK_9, CTRL_MASK | SHIFT_MASK, "insertParentheses");
    122         km.mapKey(KeyEvent.VK_0, CTRL_MASK | SHIFT_MASK, "movePastCloseAndReindent");
    123 
    124         km.mapKey(KeyEvent.VK_OPEN_BRACKET, CTRL_MASK | SHIFT_MASK, "insertBraces");
    125         // Duplicate mapping for 1.4.
    126         km.mapKey(KeyEvent.VK_BRACELEFT, CTRL_MASK | SHIFT_MASK, "insertBraces");
    127 
    128         km.mapKey(KeyEvent.VK_F12, 0, "wrapComment");
    129 
    130         // Duplicate mapping to support IBM 1.3 for Linux.
    131         km.mapKey(0xffc9, 0, "wrapComment"); // F12
    132 
    133         km.mapKey(KeyEvent.VK_OPEN_BRACKET, CTRL_MASK, "fold");
    134         km.mapKey(KeyEvent.VK_CLOSE_BRACKET, CTRL_MASK, "unfold");
    135 
    136         km.mapKey(KeyEvent.VK_F9, 0, "compile");
    137         km.mapKey(KeyEvent.VK_F9, CTRL_MASK, "recompile");
    138         km.mapKey(KeyEvent.VK_F1, ALT_MASK, "jdkHelp");
    139         km.mapKey(KeyEvent.VK_F1, CTRL_MASK, "source");
    140 
    141         // This is the "normal" mapping.
    142         km.mapKey(KeyEvent.VK_COMMA,  CTRL_MASK | SHIFT_MASK, "htmlInsertTag");
    143         // The "normal" mapping doesn't work for Linux, but this does.
    144         km.mapKey(0x7c, CTRL_MASK | SHIFT_MASK, "htmlInsertTag");
    145 
    146         if (Editor.checkExperimental()) {
    147             km.mapKey(KeyEvent.VK_SEMICOLON, ALT_MASK, "JavaMode.insertComment");
    148             km.mapKey(KeyEvent.VK_ENTER, ALT_MASK, "JavaMode.newlineAndIndentForComment");
    149         }
    150 
    151         if (Platform.isPlatformLinux()) {
    152             // Blackdown 1.1.7v3, 1.2pre2, IBM 1.1.8.
    153             // Duplicate mappings needed for VK_9, VK_0 and VK_OPEN_BRACKET.
    154             km.mapKey(0x68, CTRL_MASK | SHIFT_MASK, "insertParentheses");
    155             km.mapKey(0x69, CTRL_MASK | SHIFT_MASK, "movePastCloseAndReindent");
    156             km.mapKey(0xbb, CTRL_MASK | SHIFT_MASK, "insertBraces");
    157         }
    158     }
    159 
    160     public void populateModeMenu(Editor editor, Menu menu)
    161     {
    162         menu.add(editor, "Compile...", 'C', "compile");
    163         menu.add(editor, "Recompile", 'R', "recompile");
    164         boolean enabled = CompilationCommands.getCompilationBuffer() != null;
    165         menu.addSeparator();
    166         menu.add(editor, "Next Error", 'N', "nextError", enabled);
    167         menu.add(editor, "Previous Error", 'P', "previousError", enabled);
    168         menu.add(editor, "Show Error Message", 'M', "showMessage", enabled);
    169         menu.addSeparator();
    170         MenuItem jdbMenuItem = menu.add(editor, "Debug...", 'D', "jdb");
    171         if (jdb != null)
    172             jdbMenuItem.setEnabled(false);
    173         else {
    174             try {
    175                 Class.forName("com.sun.jdi.Bootstrap");
    176             }
    177             catch (ClassNotFoundException e) {
    178                 jdbMenuItem.setEnabled(false);
    179             }
    180         }
    181     }
    182 
    183     public JPopupMenu getContextMenu(Editor editor)
    184     {
    185         final JPopupMenu popup = new JPopupMenu();
    186         if (jdb != null) {
    187             final Line line = editor.getDotLine();
    188             if (line != null) {
    189                 final Dispatcher dispatcher = editor.getDispatcher();
    190                 JMenuItem menuItem =
    191                     addContextMenuItem("Set breakpoint",
    192                         "jdbSetBreakpoint", popup, dispatcher);
    193                 if (line.isBlank() || line.getAnnotation() != null)
    194                     menuItem.setEnabled(false);
    195                 menuItem =
    196                     addContextMenuItem("Delete breakpoint",
    197                         "jdbDeleteBreakpoint", popup, dispatcher);
    198                 if (line.getAnnotation() == null)
    199                     menuItem.setEnabled(false);
    200                 menuItem =
    201                     addContextMenuItem("Run to current line",
    202                         "jdbRunToCurrentLine", popup, dispatcher);
    203                 if (line.isBlank())
    204                     menuItem.setEnabled(false);
    205                 popup.addSeparator();
    206             }
    207         }
    208         addDefaultContextMenuItems(editor, popup);
    209         popup.pack();
    210         return popup;
    211     }
    212 
    213     public NavigationComponent getSidebarComponent(Editor editor)
    214     {
    215         if (getId() == JAVA_MODE) {
    216             View view = editor.getCurrentView();
    217             if (view == null)
    218                 return null; // Shouldn't happen.
    219             if (view.getSidebarComponent() == null)
    220                 view.setSidebarComponent(new JavaTree(editor));
    221             return view.getSidebarComponent();
    222         }
    223         // For subclasses...
    224         return super.getSidebarComponent(editor);
    225     }
    226 
    227     public Tagger getTagger(SystemBuffer buffer)
    228     {
    229         return new JavaTagger(buffer);
    230     }
    231 
    232     public boolean isTaggable()
    233     {
     1019      case '.':
    2341020        return true;
    235     }
    236 
    237     public boolean hasQualifiedNames()
    238     {
    239         return true;
    240     }
    241 
    242     public boolean isQualifiedName(String s)
    243     {
    244         return s.indexOf('.') >= 0;
    245     }
    246 
    247     public int getCorrectIndentation(final Line line, final Buffer buffer)
    248     {
    249         if (line.flags() == STATE_COMMENT)
    250             return indentComment(line, buffer);
    251         final String text = line.trim();
    252         final char textFirstChar = text.length() > 0 ? text.charAt(0) : 0;
    253         if (textFirstChar == '}')
    254             return indentClosingBrace(line, buffer);
    255 
    256         if (textFirstChar == 'c' || textFirstChar == 'd') {
    257             // Does line begin with "case" or "default"?
    258             final String firstIdentifier = getFirstIdentifier(text);
    259             if (firstIdentifier.equals("case") || firstIdentifier.equals("default"))
    260                 return indentSwitchLabel(line, buffer);
     1021      case '|':
     1022        return text.endsWith("||");
     1023      case '&':
     1024        return text.endsWith("&&");
     1025      default:
     1026        return false;
     1027      }
     1028  }
     1029
     1030  // Replaces syntactic whitespace (quotes and comments) with actual space
     1031  // characters, then returns trimmed string.
     1032  protected static String trimSyntacticWhitespace(String s)
     1033  {
     1034    JavaSyntaxIterator it = new JavaSyntaxIterator(null);
     1035    return new String(it.hideSyntacticWhitespace(s)).trim();
     1036  }
     1037
     1038  public boolean isIdentifierStart(char c)
     1039  {
     1040    return Character.isJavaIdentifierStart(c);
     1041  }
     1042
     1043  public boolean isIdentifierPart(char c)
     1044  {
     1045    return Character.isJavaIdentifierPart(c);
     1046  }
     1047
     1048  public boolean isInComment(Buffer buffer, Position pos)
     1049  {
     1050    if (buffer == null || pos == null)
     1051      {
     1052        Debug.bug();
     1053        return false;
     1054      }
     1055    final Line line = pos.getLine();
     1056    final String text = line.getText();
     1057    if (text == null)
     1058      return false;
     1059    final char[] chars = text.toCharArray();
     1060    final int offset = pos.getOffset();
     1061    if (buffer.needsParsing())
     1062      buffer.getFormatter().parseBuffer();
     1063    int state = line.flags();
     1064    final int length = chars.length;
     1065    for (int i = 0; i < length; i++)
     1066      {
     1067        if (i == offset)
     1068          return state == STATE_COMMENT;
     1069        char c = chars[i];
     1070        if (c == '\\' && i < length-1)
     1071          {
     1072            // Escape character.
     1073            continue;
     1074          }
     1075        if (state == STATE_QUOTE)
     1076          {
     1077            if (c == '"')
     1078              state = STATE_NEUTRAL;
     1079            continue;
     1080          }
     1081        if (state == STATE_SINGLEQUOTE)
     1082          {
     1083            if (c == '\'')
     1084              state = STATE_NEUTRAL;
     1085            continue;
     1086          }
     1087        if (state == STATE_COMMENT)
     1088          {
     1089            if (c == '*' && i < length-1 && chars[i+1] == '/')
     1090              {
     1091                // /* */ comment ending
     1092                state = STATE_NEUTRAL;
     1093              }
     1094            continue;
     1095          }
     1096        // Reaching here, STATE_NEUTRAL...
     1097        if (c == '"')
     1098          {
     1099            state = STATE_QUOTE;
     1100            continue;
     1101          }
     1102        if (c == '\'')
     1103          {
     1104            state = STATE_SINGLEQUOTE;
     1105            continue;
     1106          }
     1107        if (c == '/')
     1108          {
     1109            if (i < length-1)
     1110              {
     1111                if (chars[i+1] == '*')
     1112                  {
     1113                    // /* */ comment starting
     1114                    state = STATE_COMMENT;
     1115                    continue;
     1116                  }
     1117                if (chars[i+1] == '/')
     1118                  {
     1119                    // "//" comment starting
     1120                    return true;
     1121                  }
     1122              }
     1123          }
     1124      }
     1125    return state == STATE_COMMENT;
     1126  }
     1127
     1128  public boolean isCommentLine(Line line)
     1129  {
     1130    return line.trim().startsWith("//");
     1131  }
     1132
     1133  public static void insertComment()
     1134  {
     1135    if (!Editor.checkExperimental())
     1136      return;
     1137    final Editor editor = Editor.currentEditor();
     1138    String toBeInserted =
     1139      Editor.preferences().getStringProperty(Property.JAVA_MODE_INSERT_COMMENT_TEXT);
     1140    if (toBeInserted == null)
     1141      toBeInserted = "/**\\n * |\\n */";
     1142    Position caretPos = null;
     1143    CompoundEdit compoundEdit = editor.beginCompoundEdit();
     1144    final int limit = toBeInserted.length();
     1145    for (int i = 0; i < limit; i++)
     1146      {
     1147        char c = toBeInserted.charAt(i);
     1148        if (c == '|')
     1149          {
     1150            caretPos = new Position(editor.getDot());
     1151            continue;
     1152          }
     1153        if (c == '\\' && i < limit-1)
     1154          {
     1155            c = toBeInserted.charAt(++i);
     1156            if (c == 'n')
     1157              {
     1158                editor.newlineAndIndent();
     1159                continue;
     1160              }
    2611161            // Otherwise fall through...
    262         } else if (textFirstChar == 'e') {
    263             // Does line begin with "else" or "elseif"?
    264             final String firstIdentifier = getFirstIdentifier(text);
    265             if (firstIdentifier.equals("else") || firstIdentifier.equals("elseif")) {
    266                 Position match = matchElse(new Position(line, 0));
    267                 if (match != null)
    268                     return buffer.getIndentation(match.getLine());
    269             }
    270             // Otherwise fall through...
    271         } else if (textFirstChar == '"') {
    272             // If quoted text starts in column 0, leave it alone.
    273             if (line.charAt(0) == '"')
    274                 return 0;
    275         }
    276 
    277         Position paren = findEnclosingParen(new Position(line, 0));
    278         if (paren != null)
    279             return indentInParen(paren, buffer);
    280 
    281         final Line model = findModel(line);
    282         if (model == null)
    283             return 0;
    284 
    285         final int indentSize = buffer.getIndentSize();
    286 
    287         final String firstIdentifier = getFirstIdentifier(text);
    288         if (firstIdentifier.equals("throws") ||
    289             firstIdentifier.equals("implements"))
    290         {
    291             Position pos = findBeginningOfStatement(new Position(model, 0));
    292             return buffer.getIndentation(pos.getLine()) + indentSize;
    293         }
    294 
    295         final String modelText = trimSyntacticWhitespace(model.getText());
    296 
    297         // Model line can't be blank, so this is safe.
    298         final char modelLastChar = modelText.charAt(modelText.length() - 1);
    299 
    300         if (modelLastChar == '{')
    301             return indentAfterOpeningBrace(model, modelText, buffer);
    302 
    303         if (modelLastChar == ')')
    304             return indentAfterCloseParen(model, text, textFirstChar, buffer);
    305 
    306         final String lastIdentifier = getLastIdentifier(modelText);
    307         if (lastIdentifier != null && lastIdentifier.equals("else"))
    308             return indentAfterElse(model, text, textFirstChar, buffer);
    309 
    310         final char modelFirstChar = modelText.charAt(0);
    311         if (modelFirstChar == 'c' || modelFirstChar == 'd') {
    312             final String modelFirstIdentifier = getFirstIdentifier(modelText);
    313             if (modelFirstIdentifier.equals("case") ||
    314                 modelFirstIdentifier.equals("default"))
    315                 return indentAfterSwitchLabel(model, text, textFirstChar, buffer);
    316             // Otherwise fall through...
    317         }
    318 
    319         final int indent = getIndentationOfEnclosingScope(line, buffer);
    320 
    321         if (textFirstChar == '{') {
    322             if (buffer.getBooleanProperty(Property.INDENT_BEFORE_BRACE)) {
    323                 // Never indent before the opening brace of a class or method.
    324                 if (!isOpeningBraceOfClassOrMethod(line))
    325                     return indent + indentSize;
    326             }
    327             return indent;
    328         }
    329 
    330         if (textFirstChar == '=')
    331             return indent + indentSize;
    332 
    333         if (modelLastChar == ',') {
    334             if (buffer.getModeId() == CPP_MODE && modelFirstChar == ':') {
    335                 // Model line is start of member initialization list, current
    336                 // line is continuation.
    337                 return indent + 2;
    338             }
    339             if (isInArrayInitializer(line))
    340                 return indent;
    341             // Otherwise it's a continuation line.
    342             return indent + indentSize;
    343         }
    344 
    345         // Check for continuation line.
    346         if (isContinued(modelText, modelLastChar))
    347             return indent + indentSize;
    348 
    349         return indent;
    350     }
    351 
    352     private final int indentComment(Line line, Buffer buffer)
    353     {
    354         final Line model = findModel(line);
    355         if (model == null)
    356             return 0;
    357         int indent = buffer.getIndentation(model);
    358         if (model.trim().startsWith("/*"))
    359             if (line.trim().startsWith("*"))
    360                 return indent+1;
    361         return indent;
    362     }
    363 
    364     protected int indentClosingBrace(Line line, Buffer buffer)
    365     {
    366         Position pos = matchClosingBrace(new Position(line, 0));
    367         if (isOpeningBraceOfClassOrMethod(pos.getLine()))
    368             pos = findBeginningOfStatement(pos);
    369         else if (!pos.getLine().trim().startsWith("{"))
    370             pos = findPreviousConditional(pos);
    371         return buffer.getIndentation(pos.getLine());
    372     }
    373 
    374     private final int indentSwitchLabel(Line line, Buffer buffer)
    375     {
    376         Line switchLine = findSwitch(line);
    377         if (switchLine != null)
    378             return buffer.getIndentation(switchLine) + buffer.getIndentSize();
    379         return 0;
    380     }
    381 
    382     private final int indentInParen(Position posParen, Buffer buffer)
    383     {
    384         final Line line = posParen.getLine();
    385         if (line.trim().endsWith("(") || !buffer.getBooleanProperty(Property.LINEUP_ARGLIST))
    386             return buffer.getIndentation(line) + buffer.getIndentSize();
    387         final int limit = line.length();
    388         int offset = posParen.getOffset();
    389         do {
    390             ++offset;
    391         } while (offset < limit && line.charAt(offset) <= ' ');
    392         if (offset <= limit)
    393             return buffer.getCol(line, offset);
    394         return 0;
    395     }
    396 
    397     private final int indentAfterOpeningBrace(Line model, String modelText,
    398         Buffer buffer)
    399     {
    400         final int indentSize = buffer.getIndentSize();
    401         if (isOpeningBraceOfClassOrMethod(model)) {
    402             Position pos = findBeginningOfStatement(new Position(model, 0));
    403             int indent = buffer.getIndentation(pos.getLine());
    404             if (buffer.getBooleanProperty(Property.INDENT_AFTER_OPENING_BRACE))
    405                 indent += indentSize;
    406             return indent;
    407         }
    408         Position pos = new Position(model, model.length()-1);
    409         if (modelText.charAt(0) != '{')
    410             pos = findPreviousConditional(pos);
    411         int indent = buffer.getIndentation(pos.getLine());
    412         if (buffer.getBooleanProperty(Property.INDENT_AFTER_BRACE))
    413             indent += indentSize;
    414         final boolean indentBeforeBrace =
    415             buffer.getBooleanProperty(Property.INDENT_BEFORE_BRACE);
    416         if (indentBeforeBrace && pos.getLine() != model)
    417             indent += indentSize;
    418         return indent;
    419     }
    420 
    421     private final int indentAfterElse(Line model, String text,
    422         char textFirstChar, Buffer buffer)
    423     {
    424         int indent = buffer.getIndentation(model);
    425         final boolean indentBeforeBrace =
    426             buffer.getBooleanProperty(Property.INDENT_BEFORE_BRACE);
    427         if (indentBeforeBrace || textFirstChar != '{')
    428             return indent + buffer.getIndentSize();
    429         else
    430             return indent;
    431     }
    432 
    433     private final int indentAfterSwitchLabel(Line model, String text,
    434         char textFirstChar, Buffer buffer)
    435     {
    436         int indent = buffer.getIndentation(model);
    437         final boolean indentBeforeBrace =
    438             buffer.getBooleanProperty(Property.INDENT_BEFORE_BRACE);
    439         if (indentBeforeBrace || textFirstChar != '{')
    440             return indent + buffer.getIndentSize();
    441         else
    442             return indent;
    443     }
    444 
    445     private final int indentAfterCloseParen(Line model, String text,
    446         char textFirstChar, Buffer buffer)
    447     {
    448         // Find matching '('.
    449         SyntaxIterator it = getSyntaxIterator(new Position(model, model.length()));
    450         char c;
    451         do {
    452             c = it.prevChar();
    453         } while (c != SyntaxIterator.DONE && c != ')');
    454         Position pos = it.getPosition();
    455         pos = matchClosingParen(pos);
    456         boolean indent = false;
    457         final String s = getIdentifierBefore(pos);
    458         final String[] indentAfter = {"if", "while", "for", "switch", "catch"};
    459         if (Utilities.isOneOf(s, indentAfter)) {
    460             indent = true;
    461         } else if (buffer.getModeId() == JAVA_MODE) {
    462             if (s.equals("synchronized"))
    463                 indent = true;
    464         } else if (buffer.getModeId() == PHP_MODE) {
    465             if (s.equals("elseif") || s.equals("foreach"))
    466                 indent = true;
    467         }
    468         if (indent) {
    469             int modelIndent = buffer.getIndentation(pos.getLine());
    470             if (textFirstChar != '{' ||
    471                 buffer.getBooleanProperty(Property.INDENT_BEFORE_BRACE))
    472                 return modelIndent + buffer.getIndentSize();
    473             else
    474                 return modelIndent;
    475         }
    476         if (buffer.getModeId() == JAVA_MODE) {
    477             RE re = new UncheckedRE("\\s+new\\s+");
    478             if (re.getMatch(pos.getLine().getText().substring(0, pos.getOffset())) != null)
    479                 indent = true;
    480         }
    481         int modelIndent =
    482             buffer.getIndentation(findBeginningOfStatement(pos).getLine());
    483         if (indent && (textFirstChar != '{' || buffer.getBooleanProperty(Property.INDENT_BEFORE_BRACE)))
    484             return modelIndent + buffer.getIndentSize();
    485         else
    486             return modelIndent;
    487     }
    488 
    489     private final int getIndentationOfEnclosingScope(Line line, Buffer buffer)
    490     {
    491         SyntaxIterator it = getSyntaxIterator(new Position(line, 0));
    492       loop:
    493         while (true) {
    494             switch (it.prevChar()) {
    495                 case ')': {
    496                     Position pos = matchClosingParen(it.getPosition());
    497                     it = getSyntaxIterator(pos);
     1162          }
     1163        editor.insertChar(c);
     1164      }
     1165    if (caretPos != null)
     1166      editor.moveDotTo(caretPos);
     1167    editor.moveCaretToDotCol();
     1168    editor.endCompoundEdit(compoundEdit);
     1169    editor.getFormatter().parseBuffer();
     1170  }
     1171
     1172  public static void newlineAndIndentForComment()
     1173  {
     1174    final Editor editor = Editor.currentEditor();
     1175    if (!editor.checkReadOnly())
     1176      return;
     1177    final Buffer buffer  = editor.getBuffer();
     1178    final Display display = editor.getDisplay();
     1179    String commentPrefix = null;
     1180    String s = editor.getDotLine().getText().trim();
     1181    int flags = editor.getDotLine().flags();
     1182    if (flags == STATE_COMMENT)
     1183      {
     1184        if (s.startsWith("*") && !s.endsWith("*/"))
     1185          commentPrefix = "* ";
     1186      }
     1187    else
     1188      {
     1189        // Look for start of comment on current line.
     1190        if (s.startsWith("/*") && !s.endsWith("*/"))
     1191          commentPrefix = "* ";
     1192        else if (s.startsWith("//"))
     1193          commentPrefix = "// ";
     1194      }
     1195    if (commentPrefix == null)
     1196      {
     1197        // No special handling necessary.
     1198        editor.newlineAndIndent();
     1199        return;
     1200      }
     1201    CompoundEdit compoundEdit = buffer.beginCompoundEdit();
     1202    if (editor.getMark() != null)
     1203      editor.deleteRegion();
     1204    editor.addUndo(SimpleEdit.INSERT_LINE_SEP);
     1205    editor.insertLineSeparator();
     1206    // Trim leading whitespace. (This code actually trims trailing
     1207    // whitespace too.)
     1208    editor.addUndo(SimpleEdit.LINE_EDIT);
     1209    editor.getDotLine().setText(editor.getDotLine().getText().trim());
     1210    // Insert the comment prefix at the start of the new line.
     1211    editor.addUndo(SimpleEdit.INSERT_STRING);
     1212    editor.insertStringInternal(commentPrefix);
     1213    // Make the indentation code think we're still in a multi-line
     1214    // comment, if we were in one before.
     1215    editor.getDotLine().setFlags(flags);
     1216    editor.indentLine();
     1217    // We want dot to end up right after the comment prefix.
     1218    editor.moveDotToIndentation();
     1219    editor.getDot().skip(commentPrefix.length());
     1220    display.moveCaretToDotCol();
     1221    buffer.endCompoundEdit(compoundEdit);
     1222  }
     1223
     1224  public String getToolTipText(Editor editor, MouseEvent e)
     1225  {
     1226    if (editor.getModeId() == JAVA_MODE)
     1227      {
     1228        if (editor.getBuffer().getBooleanProperty(Property.ENABLE_TOOL_TIPS))
     1229          {
     1230            Position pos =
     1231              editor.getDisplay().positionFromPoint(e.getPoint());
     1232            if (pos != null)
     1233              {
     1234                final String name = getQualifiedName(pos);
     1235                if (name != null)
     1236                  {
     1237                    JavaContext context = new JavaContext(editor);
     1238                    context.parseContext(pos);
     1239                    JavaVariable var = context.findDeclaration(name);
     1240                    if (var != null)
     1241                      return var.toString();
     1242                  }
     1243              }
     1244          }
     1245      }
     1246    return null;
     1247  }
     1248
     1249  private String getQualifiedName(Position pos)
     1250  {
     1251    Line line = pos.getLine();
     1252    int offset = pos.getOffset();
     1253    final int limit = line.length();
     1254    if (offset < limit)
     1255      {
     1256        char c = line.charAt(offset);
     1257        if (isIdentifierPart(c))
     1258          {
     1259            while (offset > 0)
     1260              {
     1261                --offset;
     1262                c = line.charAt(offset);
     1263                if (!isIdentifierPart(c) && c != '.')
     1264                  {
     1265                    ++offset;
    4981266                    break;
    499                 }
    500                 case '}': {
    501                     Position pos = matchClosingBrace(it.getPosition());
    502                     if (pos.getOffset() == 0)
    503                         return 0;
    504                     pos = findBeginningOfStatement(pos);
    505                     return buffer.getIndentation(pos.getLine());
    506                 }
    507                 case '{': {
    508                     Line model = it.getLine();
    509                     String modelText = trimSyntacticWhitespace(model.getText());
    510                     if (modelText.equals("{"))
    511                         return buffer.getIndentation(model) + buffer.getIndentSize();
    512                     return indentAfterOpeningBrace(model, modelText, buffer);
    513                 }
    514                 case ':': {
    515                     String firstIdentifier = getFirstIdentifier(it.getLine());
    516                     if (firstIdentifier.equals("case") || firstIdentifier.equals("default"))
    517                         return buffer.getIndentation(it.getLine()) + buffer.getIndentSize();
    518                     break;
    519                 }
    520                 case SyntaxIterator.DONE:
    521                     return 0;
    522             }
    523         }
    524     }
    525 
    526     private boolean isInArrayInitializer(Line line)
    527     {
    528         // Find matching opening brace.
    529         Position match = matchClosingBrace(new Position(line, 0));
    530         SyntaxIterator it = getSyntaxIterator(match);
    531         char c;
    532         do {
    533             c = it.prevChar();
    534         } while (c != SyntaxIterator.DONE && Character.isWhitespace(c));
    535         if (c == '=' || c == ']')
    536             return true;
    537         return false;
    538     }
    539 
    540     protected static Line findModel(Line line)
    541     {
    542         Line model = line.previous();
    543         if (line.flags() == STATE_COMMENT) {
    544             // Any non-blank line is an acceptable model.
    545             while (model != null && model.isBlank())
    546                 model = model.previous();
    547         } else {
    548             while (model != null) {
    549                 if (isAcceptableModel(model))
    550                     break; // Found an acceptable model.
     1267                  }
     1268              }
     1269            // Now we're looking at the first character of the identifier.
     1270            c = line.charAt(offset);
     1271            if (isIdentifierStart(c))
     1272              {
     1273                FastStringBuffer sb = new FastStringBuffer();
     1274                sb.append(c);
     1275                while (++offset < limit)
     1276                  {
     1277                    c = line.charAt(offset);
     1278                    if (isIdentifierPart(c))
     1279                      {
     1280                        sb.append(c);
     1281                      }
     1282                    else if (c == '.' && offset < pos.getOffset())
     1283                      {
     1284                        // We don't want to go beyond the end of the
     1285                        // simple name at pos.
     1286                        sb.append(c);
     1287                      }
     1288                    else
     1289                      break;
     1290                  }
     1291                return sb.toString();
     1292              }
     1293          }
     1294      }
     1295    return null;
     1296  }
     1297
     1298  public Expression getExpressionAtDot(final Editor editor, final boolean exact)
     1299  {
     1300    if (editor.getModeId() == OBJC_MODE)
     1301      return super.getExpressionAtDot(editor, exact);
     1302    if (editor.getDot() == null)
     1303      return null;
     1304    Position begin;
     1305    if (editor.getMark() != null)
     1306      {
     1307        // Start at beginning of marked block.
     1308        Region r = new Region(editor);
     1309        begin = r.getBegin();
     1310      }
     1311    else
     1312      begin = editor.getDot();
     1313    final Line line = begin.getLine();
     1314    final int offset = begin.getOffset();
     1315    Position posExpr = null;
     1316    if (exact)
     1317      {
     1318        if (offset < line.length() && isIdentifierPart(line.charAt(offset)))
     1319          posExpr = findIdentifierStart(line, offset);
     1320        if (posExpr == null)
     1321          return null;
     1322      }
     1323    if (posExpr == null)
     1324      {
     1325        // Not exact, or no identifier there.  Try to be smart.
     1326        RE re = new UncheckedRE("([A-Za-z_$]+[A-Za-z_$0-9]*)\\s*\\(");
     1327        final String text = editor.getDotLine().getText();
     1328        int index = 0;
     1329        REMatch  match;
     1330        while ((match = re.getMatch(text, index)) != null)
     1331          {
     1332            String identifier = match.toString(1);
     1333            if (!isKeyword(identifier))
     1334              {
     1335                posExpr = new Position(line, match.getStartIndex());
     1336                // If we've found a match to the right of dot, we're done.
     1337                if (match.getEndIndex() > offset)
     1338                  break;
     1339              }
     1340            index = match.getEndIndex();
     1341          }
     1342      }
     1343    if (posExpr == null)
     1344      {
     1345        // Smart didn't help.  Go back to exact.
     1346        if (offset < line.length() && isIdentifierStart(line.charAt(offset)))
     1347          posExpr = findIdentifierStart(line, offset);
     1348      }
     1349    if (posExpr == null)
     1350      return null;
     1351    Position pos = posExpr.copy();
     1352    // Gather up method name.
     1353    FastStringBuffer sb = new FastStringBuffer();
     1354    while (true)
     1355      {
     1356        char c = pos.getChar();
     1357        if (!isIdentifierPart(c))
     1358          break;
     1359        sb.append(c);
     1360        if (!pos.next())
     1361          break;
     1362      }
     1363    String name = sb.toString().trim();
     1364    // Skip whitespace (if any) between identifier and '('.
     1365    while (true)
     1366      {
     1367        char c = pos.getChar();
     1368        if (!Character.isWhitespace(c))
     1369          break;
     1370        if (!pos.next())
     1371          break;
     1372      }
     1373    final int arity;
     1374    if (editor.getModeId() == JAVASCRIPT_MODE)
     1375      arity = -1; // Can't trust arity in JavaScript.
     1376    else
     1377      arity = getArity(editor, pos);
     1378    if (arity >= 0)
     1379      return new JavaExpression(name, arity);
     1380    else
     1381      return new JavaExpression(name, arity, TAG_UNKNOWN);
     1382  }
     1383
     1384  private int getArity(Editor editor, Position pos)
     1385  {
     1386    if (pos.getChar() != '(')
     1387      return -1;
     1388    if (!pos.next())
     1389      return -1;
     1390    final Position start = pos.copy();
     1391    int parenCount = 0;
     1392    int arity = 0;
     1393    char quoteChar = '\0';
     1394    boolean inQuote = false;
     1395    while (!pos.atEnd())
     1396      {
     1397        char c = pos.getChar();
     1398        if (inQuote)
     1399          {
     1400            if (c == quoteChar)
     1401              inQuote = false;
     1402            pos.next();
     1403            continue;
     1404          }
     1405        // Not in a quoted string.
     1406        if (c == '"' || c == '\'')
     1407          {
     1408            inQuote = true;
     1409            quoteChar = c;
     1410            pos.next();
     1411            continue;
     1412          }
     1413        if (c == ',')
     1414          {
     1415            if (parenCount == 0) // Top level.
     1416              ++arity;
     1417            pos.next();
     1418            continue;
     1419          }
     1420        if (c == '(')
     1421          {
     1422            ++parenCount;
     1423            pos.next();
     1424            continue;
     1425          }
     1426        if (c == ')')
     1427          {
     1428            --parenCount;
     1429            if (parenCount < 0)
     1430              {
     1431                // Closing paren, done.
     1432                if (arity == 0)
     1433                  {
     1434                    // We haven't seen a comma.
     1435                    Region r = new Region(editor.getBuffer(), start, pos);
     1436                    if (r.toString().trim().length() > 0)
     1437                      arity = 1;
     1438                  }
    5511439                else
    552                     model = model.previous();
    553             }
    554         }
    555         return model;
    556     }
    557 
    558     private static final boolean isAcceptableModel(Line line)
    559     {
    560         int flags = line.flags();
    561         if (flags == STATE_COMMENT || flags == STATE_QUOTE)
    562             return false;
    563         if (line.isBlank())
    564             return false;
    565         String trim = line.trim();
    566         char firstChar = trim.charAt(0);
    567         if (firstChar == '/') {
    568             if (trim.length() > 1 && trim.charAt(1) =='/')
    569                 return false;
    570         } else if (firstChar == '#')
    571             return false;
    572         else if (firstChar == '=')
    573             return false;
    574         else if (firstChar == ':') {
    575             if (trim.startsWith(": ") || trim.startsWith(":\t"))
    576                 return false;
    577         }
    578         String s = trimSyntacticWhitespace(line.getText());
    579         if (s.length() == 0)
    580             return false;
    581         return true;
    582     }
    583 
    584     // Returns true if line contains opening brace of class or method.
    585     private boolean isOpeningBraceOfClassOrMethod(Line line)
    586     {
    587         if (line.length() == 0)
    588             return false;
    589         String text = trimSyntacticWhitespace(line.getText());
    590         if (!text.endsWith("{"))
    591             return false;
    592         if (text.equals("{")) {
    593             Line modelLine = findModel(line);
    594             if (modelLine == null)
    595                 return true;
    596             Position beginningOfStatement =
    597                 findBeginningOfStatement(new Position(modelLine, 0));
    598             text = beginningOfStatement.getLine().trim();
    599         } else
    600             text = text.substring(0, text.length()-1).trim();
    601         if (text.indexOf('=') >= 0)
    602             return false;
    603         final String firstIdentifier = getFirstIdentifier(text);
    604         if (Utilities.isOneOf(firstIdentifier, conditionals))
    605             return false;
    606         if (firstIdentifier.equals("case") || firstIdentifier.equals("default"))
    607             return false;
    608         return true;
    609     }
    610 
    611     protected String getFirstIdentifier(String s)
    612     {
    613         return Utilities.getFirstIdentifier(s, this);
    614     }
    615 
    616     protected final String getFirstIdentifier(Line line)
    617     {
    618         return getFirstIdentifier(line.trim());
    619     }
    620 
    621     protected final String getLastIdentifier(String s)
    622     {
    623         int i = s.length()-1;
    624         while (i >= 0) {
    625             if (isIdentifierPart(s.charAt(i))) {
    626                 if (i > 0)
    627                     --i;
    628                 else
    629                     break;
    630             } else {
    631                 ++i;
    632                 break;
    633             }
    634         }
    635         if (i >= 0 && i < s.length())
    636             return s.substring(i);
    637         return null;
    638     }
    639 
    640     private final String getIdentifierBefore(Position pos)
    641     {
    642         while (pos.prev())
    643             if (!Character.isWhitespace(pos.getChar()))
    644                 break;
    645         while (isIdentifierPart(pos.getChar()) && pos.prev())
    646             ;
    647         while (!isIdentifierStart(pos.getChar()) && pos.next())
    648             ;
    649         return pos.getIdentifier(this);
    650     }
    651 
    652     protected final Position matchClosingBrace(Position start)
    653     {
    654         SyntaxIterator it = getSyntaxIterator(start);
    655         int count = 1;
    656         while (true) {
    657             switch (it.prevChar()) {
    658                 case '}':
    659                     ++count;
    660                     break;
    661                 case '{':
    662                     --count;
    663                     if (count == 0)
    664                         return it.getPosition();
    665                     break;
    666                 case SyntaxIterator.DONE:
    667                     return it.getPosition();
    668                 default:
    669                     break;
    670             }
    671         }
    672     }
    673 
    674     protected final Position matchClosingParen(Position start)
    675     {
    676         SyntaxIterator it = getSyntaxIterator(start);
    677         int count = 1;
    678         while (true) {
    679             switch (it.prevChar()) {
    680                 case ')':
    681                     ++count;
    682                     break;
    683                 case '(':
    684                     --count;
    685                     if (count == 0)
    686                         return it.getPosition();
    687                     break;
    688                 case SyntaxIterator.DONE:
    689                     return it.getPosition();
    690                 default:
    691                     break;
    692             }
    693         }
    694     }
    695 
    696     // Scan backwards from starting position, looking for unmatched opening
    697     // parenthesis.
    698     protected Position findEnclosingParen(Position start)
    699     {
    700         SyntaxIterator it = getSyntaxIterator(start);
    701         int parenCount = 0;
    702         int braceCount = 0;
    703         boolean seenBrace = false;
    704         while (true) {
    705             switch (it.prevChar()) {
    706                 case '{':
    707                     if (braceCount == 0)
    708                         return null; // Found unmatched '{'.
    709                     --braceCount;
    710                     seenBrace = true;
    711                     break;
    712                 case '}':
    713                     ++braceCount;
    714                     seenBrace = true;
    715                     break;
    716                 case ';':
    717                     if (seenBrace)
    718                         return null;
    719                     break;
    720                 case ')':
    721                     ++parenCount;
    722                     break;
    723                 case '(':
    724                     if (parenCount == 0)
    725                         return it.getPosition(); // Found unmatched '('.
    726                     --parenCount;
    727                     break;
    728                 case SyntaxIterator.DONE:
    729                     return null;
    730                 default:
    731                     break;
    732             }
    733         }
    734     }
    735 
    736     private final Position findEnclosingBrace(Position start)
    737     {
    738         SyntaxIterator it = getSyntaxIterator(start);
    739         int count = 0;
    740         while (true) {
    741             switch (it.prevChar()) {
    742                 case '}':
    743                     ++count;
    744                     break;
    745                 case '{':
    746                     if (count == 0)
    747                         return it.getPosition(); // Found unmatched '{'.
    748                     --count;
    749                     break;
    750                 case SyntaxIterator.DONE:
    751                     return null;
    752                 default:
    753                     break;
    754             }
    755         }
    756     }
    757 
    758     // Scan backwards from line, looking for the start of a switch statement.
    759     protected final Line findSwitch(Line line)
    760     {
    761         Position pos = findEnclosingBrace(new Position(line, 0));
    762         if (pos != null) {
    763             line = pos.getLine();
    764             do {
    765                 String s = getFirstIdentifier(line);
    766                 if (s.equals("switch"))
    767                     return line;
    768             } while ((line = line.previous()) != null);
    769         }
    770         return null;
    771     }
    772 
    773     private Position matchElse(Position start)
    774     {
    775         SyntaxIterator it = getSyntaxIterator(start);
    776         int count = 1;
    777         char c;
    778         while ((c = it.prevChar()) != SyntaxIterator.DONE) {
    779             if (c == '}') {
    780                 Position match = matchClosingBrace(it.getPosition());
    781                 it = getSyntaxIterator(match);
    782                 continue;
    783             }
    784             if (c == 'e') {
    785                 Position pos = it.getPosition();
    786                 if (pos.getIdentifier(this).equals("else")) {
    787                     ++count;
    788                     continue;
    789                 }
    790             }
    791             if (c == 'i') {
    792                 Position pos = it.getPosition();
    793                 if (pos.getIdentifier(this).equals("if")) {
    794                     --count;
    795                     if (count == 0)
    796                         return pos;
    797                     continue;
    798                 }
    799             }
    800         }
    801         return null;
    802     }
    803 
    804     public Position findBeginningOfStatement(Position start)
    805     {
    806         Position pos = new Position(start);
    807 
    808         final Position posParen = findEnclosingParen(pos);
    809 
    810         if (posParen != null)
    811             pos = posParen;
    812 
    813         final String trim = trimSyntacticWhitespace(pos.getLine().getText());
    814         final String lastIdentifier = getLastIdentifier(trim);
    815         if (lastIdentifier != null && lastIdentifier.equals("else"))
    816             return new Position(pos.getLine(), 0); // BUG!! This is clearly wrong!
    817         final String firstIdentifier = getFirstIdentifier(trim);
    818         if (firstIdentifier != null &&
    819             (firstIdentifier.equals("case") || firstIdentifier.equals("default")))
    820             return new Position(pos.getLine(), 0);
    821 
    822         while (pos.getLine().trim().startsWith("}") && pos.getPreviousLine() != null) {
    823             pos.moveTo(pos.getPreviousLine(), pos.getPreviousLine().length());
    824             pos = matchClosingBrace(pos);
    825         }
    826 
    827         SyntaxIterator it = getSyntaxIterator(pos);
    828         boolean inParen = false;
    829         int count = 0;
    830         while (true) {
    831             char c = it.prevChar();
    832             if (c == SyntaxIterator.DONE)
    833                 return it.getPosition();
    834             if (inParen) {
    835                 if (c == ')')
    836                     ++count;
    837                 else if (c == '(') {
    838                     --count;
    839                     if (count == 0) // Found it!
    840                         inParen = false;
    841                 }
    842                 continue;
    843             }
    844             if (c == ')') {
    845                 inParen = true;
    846                 count = 1;
    847                 continue;
    848             }
    849             if (c == '{') {
    850                 // If previous non-whitespace char is '=' then this is an array
    851                 // initializer.
    852                 pos = it.getPosition(); // Save position.
    853                 char ch;
    854                 do {
    855                     ch = it.prevChar();
    856                 } while (ch != SyntaxIterator.DONE && Character.isWhitespace(ch));
    857                 if (ch == '=' || ch == ']') {
    858                     // It is an array initializer.
    859                     pos = it.getPosition();
    860                     pos.moveTo(pos.getLine(), 0);
    861                     return pos;
    862                 }
    863                 // Not an array initializer.
    864                 it = getSyntaxIterator(pos); // Restore position.
    865                 // Fall through...
    866             }
    867             if (";{}:".indexOf(c) >= 0) {
    868                 do {
    869                     c = it.nextChar();
    870                 } while (c != SyntaxIterator.DONE && Character.isWhitespace(c));
    871                 pos = it.getPosition();
    872                 pos.setOffset(0);
    873                 return pos;
    874             }
    875         }
    876     }
    877 
    878     public Position findPreviousConditional(Position start)
    879     {
    880         Position pos = start.copy();
    881         Position posParen = findEnclosingParen(pos);
    882         if (posParen != null)
    883             pos = posParen;
    884         while (pos.getLine().trim().startsWith("}") && pos.getPreviousLine() != null) {
    885             pos.moveTo(pos.getPreviousLine(), pos.getPreviousLine().length());
    886             pos = matchClosingBrace(pos);
    887         }
    888         while (true) {
    889             if (pos.getLine().flags() != STATE_COMMENT) {
    890                 String text = pos.getLine().trim();
    891                 // Handle "} else".
    892                 if (text.startsWith("}"))
    893                     text = text.substring(1).trim();
    894                 String firstIdentifier = getFirstIdentifier(text);
    895                 if (Utilities.isOneOf(firstIdentifier, conditionals)) {
    896                     pos.setOffset(pos.getLine().getText().indexOf(firstIdentifier));
    897                     return pos;
    898                 }
    899             }
    900             Line previousLine = pos.getPreviousLine();
    901             if (previousLine == null)
    902                 return new Position(pos.getLine(), 0);
    903             pos.moveTo(previousLine, previousLine.length());
    904             if (pos.getLine().flags() == STATE_COMMENT)
    905                 continue;
    906             posParen = findEnclosingParen(pos);
    907             if (posParen != null) {
    908                 pos = posParen;
    909                 continue;
    910             }
    911             String s = trimSyntacticWhitespace(pos.getLine().getText());
    912             if (s.length() > 0) {
    913                 if (s.charAt(0) == '#') // C preprocessor.
    914                     break;
    915                 char lastChar = s.charAt(s.length()-1);
    916                 if (lastChar == ';' || lastChar == '{' || lastChar == '}' ||
    917                     lastChar == ':')
    918                     break;
    919             }
    920         }
    921         // No conditional found.
    922         return start;
    923     }
    924 
    925     protected final boolean isContinued(String text, char lastChar)
    926     {
    927         switch (lastChar) {
    928             case '+':
    929                 return !text.endsWith("++");
    930             case '/':
    931                 return !text.endsWith("//");
    932             case '=':
    933                 return (!text.endsWith("==") && !text.endsWith("!="));
    934             case ',':
    935                 return true;
    936             case '.':
    937                 return true;
    938             case '|':
    939                 return text.endsWith("||");
    940             case '&':
    941                 return text.endsWith("&&");
    942             default:
    943                 return false;
    944         }
    945     }
    946 
    947     // Replaces syntactic whitespace (quotes and comments) with actual space
    948     // characters, then returns trimmed string.
    949     protected static String trimSyntacticWhitespace(String s)
    950     {
    951         JavaSyntaxIterator it = new JavaSyntaxIterator(null);
    952         return new String(it.hideSyntacticWhitespace(s)).trim();
    953     }
    954 
    955     public boolean isIdentifierStart(char c)
    956     {
    957         return Character.isJavaIdentifierStart(c);
    958     }
    959 
    960     public boolean isIdentifierPart(char c)
    961     {
    962         return Character.isJavaIdentifierPart(c);
    963     }
    964 
    965     public boolean isInComment(Buffer buffer, Position pos)
    966     {
    967         if (buffer == null || pos == null) {
    968             Debug.bug();
    969             return false;
    970         }
    971         final Line line = pos.getLine();
    972         final String text = line.getText();
    973         if (text == null)
    974             return false;
    975         final char[] chars = text.toCharArray();
    976         final int offset = pos.getOffset();
    977         if (buffer.needsParsing())
    978             buffer.getFormatter().parseBuffer();
    979         int state = line.flags();
    980         final int length = chars.length;
    981         for (int i = 0; i < length; i++) {
    982             if (i == offset)
    983                 return state == STATE_COMMENT;
    984             char c = chars[i];
    985             if (c == '\\' && i < length-1) {
    986                 // Escape character.
    987                 continue;
    988             }
    989             if (state == STATE_QUOTE) {
    990                 if (c == '"')
    991                     state = STATE_NEUTRAL;
    992                 continue;
    993             }
    994             if (state == STATE_SINGLEQUOTE) {
    995                 if (c == '\'')
    996                     state = STATE_NEUTRAL;
    997                 continue;
    998             }
    999             if (state == STATE_COMMENT) {
    1000                 if (c == '*' && i < length-1 && chars[i+1] == '/') {
    1001                     // /* */ comment ending
    1002                     state = STATE_NEUTRAL;
    1003                 }
    1004                 continue;
    1005             }
    1006             // Reaching here, STATE_NEUTRAL...
    1007             if (c == '"') {
    1008                 state = STATE_QUOTE;
    1009                 continue;
    1010             }
    1011             if (c == '\'') {
    1012                 state = STATE_SINGLEQUOTE;
    1013                 continue;
    1014             }
    1015             if (c == '/') {
    1016                 if (i < length-1) {
    1017                     if (chars[i+1] == '*') {
    1018                         // /* */ comment starting
    1019                         state = STATE_COMMENT;
    1020                         continue;
    1021                     }
    1022                     if (chars[i+1] == '/') {
    1023                         // "//" comment starting
    1024                         return true;
    1025                     }
    1026                 }
    1027             }
    1028         }
    1029         return state == STATE_COMMENT;
    1030     }
    1031 
    1032     public boolean isCommentLine(Line line)
    1033     {
    1034         return line.trim().startsWith("//");
    1035     }
    1036 
    1037     public static void insertComment()
    1038     {
    1039         if (!Editor.checkExperimental())
    1040             return;
    1041         final Editor editor = Editor.currentEditor();
    1042         String toBeInserted =
    1043             Editor.preferences().getStringProperty(Property.JAVA_MODE_INSERT_COMMENT_TEXT);
    1044         if (toBeInserted == null)
    1045             toBeInserted = "/**\\n * |\\n */";
    1046         Position caretPos = null;
    1047         CompoundEdit compoundEdit = editor.beginCompoundEdit();
    1048         final int limit = toBeInserted.length();
    1049         for (int i = 0; i < limit; i++) {
    1050             char c = toBeInserted.charAt(i);
    1051             if (c == '|') {
    1052                 caretPos = new Position(editor.getDot());
    1053                 continue;
    1054             }
    1055             if (c == '\\' && i < limit-1) {
    1056                 c = toBeInserted.charAt(++i);
    1057                 if (c == 'n') {
    1058                     editor.newlineAndIndent();
    1059                     continue;
    1060                 }
    1061                 // Otherwise fall through...
    1062             }
    1063             editor.insertChar(c);
    1064         }
    1065         if (caretPos != null)
    1066             editor.moveDotTo(caretPos);
    1067         editor.moveCaretToDotCol();
    1068         editor.endCompoundEdit(compoundEdit);
    1069         editor.getFormatter().parseBuffer();
    1070     }
    1071 
    1072     public static void newlineAndIndentForComment()
    1073     {
    1074         final Editor editor = Editor.currentEditor();
    1075         if (!editor.checkReadOnly())
    1076             return;
    1077         final Buffer buffer  = editor.getBuffer();
    1078         final Display display = editor.getDisplay();
    1079         String commentPrefix = null;
    1080         String s = editor.getDotLine().getText().trim();
    1081         int flags = editor.getDotLine().flags();
    1082         if (flags == STATE_COMMENT) {
    1083             if (s.startsWith("*") && !s.endsWith("*/"))
    1084                 commentPrefix = "* ";
    1085         } else {
    1086             // Look for start of comment on current line.
    1087             if (s.startsWith("/*") && !s.endsWith("*/"))
    1088                 commentPrefix = "* ";
    1089             else if (s.startsWith("//"))
    1090                 commentPrefix = "// ";
    1091         }
    1092         if (commentPrefix == null){
    1093             // No special handling necessary.
    1094             editor.newlineAndIndent();
    1095             return;
    1096         }
    1097         CompoundEdit compoundEdit = buffer.beginCompoundEdit();
    1098         if (editor.getMark() != null)
    1099             editor.deleteRegion();
    1100         editor.addUndo(SimpleEdit.INSERT_LINE_SEP);
    1101         editor.insertLineSeparator();
    1102         // Trim leading whitespace. (This code actually trims trailing
    1103         // whitespace too.)
    1104         editor.addUndo(SimpleEdit.LINE_EDIT);
    1105         editor.getDotLine().setText(editor.getDotLine().getText().trim());
    1106         // Insert the comment prefix at the start of the new line.
    1107         editor.addUndo(SimpleEdit.INSERT_STRING);
    1108         editor.insertStringInternal(commentPrefix);
    1109         // Make the indentation code think we're still in a multi-line
    1110         // comment, if we were in one before.
    1111         editor.getDotLine().setFlags(flags);
    1112         editor.indentLine();
    1113         // We want dot to end up right after the comment prefix.
    1114         editor.moveDotToIndentation();
    1115         editor.getDot().skip(commentPrefix.length());
    1116         display.moveCaretToDotCol();
    1117         buffer.endCompoundEdit(compoundEdit);
    1118     }
    1119 
    1120     public String getToolTipText(Editor editor, MouseEvent e)
    1121     {
    1122         if (editor.getModeId() == JAVA_MODE) {
    1123             if (editor.getBuffer().getBooleanProperty(Property.ENABLE_TOOL_TIPS)) {
    1124                 Position pos =
    1125                     editor.getDisplay().positionFromPoint(e.getPoint());
    1126                 if (pos != null) {
    1127                     final String name = getQualifiedName(pos);
    1128                     if (name != null) {
    1129                         JavaContext context = new JavaContext(editor);
    1130                         context.parseContext(pos);
    1131                         JavaVariable var = context.findDeclaration(name);
    1132                         if (var != null)
    1133                             return var.toString();
    1134                     }
    1135                 }
    1136             }
    1137         }
    1138         return null;
    1139     }
    1140 
    1141     private String getQualifiedName(Position pos)
    1142     {
    1143         Line line = pos.getLine();
    1144         int offset = pos.getOffset();
    1145         final int limit = line.length();
    1146         if (offset < limit) {
    1147             char c = line.charAt(offset);
    1148             if (isIdentifierPart(c)) {
    1149                 while (offset > 0) {
    1150                     --offset;
    1151                     c = line.charAt(offset);
    1152                     if (!isIdentifierPart(c) && c != '.') {
    1153                         ++offset;
    1154                         break;
    1155                     }
    1156                 }
    1157                 // Now we're looking at the first character of the identifier.
    1158                 c = line.charAt(offset);
    1159                 if (isIdentifierStart(c)) {
    1160                     FastStringBuffer sb = new FastStringBuffer();
    1161                     sb.append(c);
    1162                     while (++offset < limit) {
    1163                         c = line.charAt(offset);
    1164                         if (isIdentifierPart(c)) {
    1165                             sb.append(c);
    1166                         } else if (c == '.' && offset < pos.getOffset()) {
    1167                             // We don't want to go beyond the end of the
    1168                             // simple name at pos.
    1169                             sb.append(c);
    1170                         } else
    1171                             break;
    1172                     }
    1173                     return sb.toString();
    1174                 }
    1175             }
    1176         }
    1177         return null;
    1178     }
    1179 
    1180     public Expression getExpressionAtDot(final Editor editor, final boolean exact)
    1181     {
    1182         if (editor.getModeId() == OBJC_MODE)
    1183             return super.getExpressionAtDot(editor, exact);
    1184         if (editor.getDot() == null)
    1185             return null;
    1186         Position begin;
    1187         if (editor.getMark() != null) {
    1188             // Start at beginning of marked block.
    1189             Region r = new Region(editor);
    1190             begin = r.getBegin();
    1191         } else
    1192             begin = editor.getDot();
    1193         final Line line = begin.getLine();
    1194         final int offset = begin.getOffset();
    1195         Position posExpr = null;
    1196         if (exact) {
    1197             if (offset < line.length() && isIdentifierPart(line.charAt(offset)))
    1198                 posExpr = findIdentifierStart(line, offset);
    1199             if (posExpr == null)
    1200                 return null;
    1201         }
    1202         if (posExpr == null) {
    1203             // Not exact, or no identifier there.  Try to be smart.
    1204             RE re = new UncheckedRE("([A-Za-z_$]+[A-Za-z_$0-9]*)\\s*\\(");
    1205             final String text = editor.getDotLine().getText();
    1206             int index = 0;
    1207             REMatch  match;
    1208             while ((match = re.getMatch(text, index)) != null) {
    1209                 String identifier = match.toString(1);
    1210                 if (!isKeyword(identifier)) {
    1211                     posExpr = new Position(line, match.getStartIndex());
    1212                     // If we've found a match to the right of dot, we're done.
    1213                     if (match.getEndIndex() > offset)
    1214                         break;
    1215                 }
    1216                 index = match.getEndIndex();
    1217             }
    1218         }
    1219         if (posExpr == null) {
    1220             // Smart didn't help.  Go back to exact.
    1221             if (offset < line.length() && isIdentifierStart(line.charAt(offset)))
    1222                 posExpr = findIdentifierStart(line, offset);
    1223         }
    1224         if (posExpr == null)
    1225             return null;
    1226         Position pos = posExpr.copy();
    1227         // Gather up method name.
    1228         FastStringBuffer sb = new FastStringBuffer();
    1229         while (true) {
    1230             char c = pos.getChar();
    1231             if (!isIdentifierPart(c))
    1232                 break;
    1233             sb.append(c);
    1234             if (!pos.next())
    1235                 break;
    1236         }
    1237         String name = sb.toString().trim();
    1238         // Skip whitespace (if any) between identifier and '('.
    1239         while (true) {
    1240             char c = pos.getChar();
    1241             if (!Character.isWhitespace(c))
    1242                 break;
    1243             if (!pos.next())
    1244                 break;
    1245         }
    1246         final int arity;
    1247         if (editor.getModeId() == JAVASCRIPT_MODE)
    1248             arity = -1; // Can't trust arity in JavaScript.
    1249         else
    1250             arity = getArity(editor, pos);
    1251         if (arity >= 0)
    1252             return new JavaExpression(name, arity);
    1253         else
    1254             return new JavaExpression(name, arity, TAG_UNKNOWN);
    1255     }
    1256 
    1257     private int getArity(Editor editor, Position pos)
    1258     {
    1259         if (pos.getChar() != '(')
    1260             return -1;
    1261         if (!pos.next())
    1262             return -1;
    1263         final Position start = pos.copy();
    1264         int parenCount = 0;
    1265         int arity = 0;
    1266         char quoteChar = '\0';
    1267         boolean inQuote = false;
    1268         while (!pos.atEnd()) {
    1269             char c = pos.getChar();
    1270             if (inQuote) {
    1271                 if (c == quoteChar)
    1272                     inQuote = false;
    1273                 pos.next();
    1274                 continue;
    1275             }
    1276             // Not in a quoted string.
    1277             if (c == '"' || c == '\'') {
    1278                 inQuote = true;
    1279                 quoteChar = c;
    1280                 pos.next();
    1281                 continue;
    1282             }
    1283             if (c == ',') {
    1284                 if (parenCount == 0) // Top level.
    1285                     ++arity;
    1286                 pos.next();
    1287                 continue;
    1288             }
    1289             if (c == '(') {
    1290                 ++parenCount;
    1291                 pos.next();
    1292                 continue;
    1293             }
    1294             if (c == ')') {
    1295                 --parenCount;
    1296                 if (parenCount < 0) {
    1297                     // Closing paren, done.
    1298                     if (arity == 0) {
    1299                         // We haven't seen a comma.
    1300                         Region r = new Region(editor.getBuffer(), start, pos);
    1301                         if (r.toString().trim().length() > 0)
    1302                             arity = 1;
    1303                     } else
    1304                         ++arity;
    1305                     return arity;
    1306                 }
    1307                 pos.next();
    1308                 continue;
    1309             }
     1440                  ++arity;
     1441                return arity;
     1442              }
    13101443            pos.next();
    1311         }
    1312         return -1;
    1313     }
     1444            continue;
     1445          }
     1446        pos.next();
     1447      }
     1448    return -1;
     1449  }
    13141450}
Note: See TracChangeset for help on using the changeset viewer.