? org/netbeans/editor/AbbrevVariable.java Index: org/netbeans/editor/Abbrev.java =================================================================== RCS file: /cvs/editor/libsrc/org/netbeans/editor/Abbrev.java,v retrieving revision 1.22 diff -u -r1.22 Abbrev.java --- org/netbeans/editor/Abbrev.java 20 Dec 2000 16:15:05 -0000 1.22 +++ org/netbeans/editor/Abbrev.java 28 Nov 2002 00:58:43 -0000 @@ -14,15 +14,23 @@ package org.netbeans.editor; import java.awt.event.ActionEvent; +import java.awt.event.InputEvent; +import java.awt.event.KeyEvent; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; +import java.util.ArrayList; import java.util.Map; import java.util.HashMap; import java.util.Iterator; +import javax.swing.AbstractAction; import javax.swing.Action; +import javax.swing.KeyStroke; import javax.swing.text.BadLocationException; import javax.swing.text.JTextComponent; import javax.swing.text.Caret; +import javax.swing.text.Position; +import org.apache.regexp.RE; +import org.apache.regexp.RESyntaxException; /** Abbreviation support allowing to expand defined character sequences * into the expanded strings or call the arbitrary action. @@ -33,6 +41,14 @@ public class Abbrev implements SettingsChangeListener, PropertyChangeListener { + private static final int STOP_COMPLETION = 0; + private static final int NEXT_VARIABLE = 1; + private static final int PREVIOUS_VARIABLE = 2; + + private static final String NEXT_VARIABLE_ACTION = "abbrev_next_var"; + private static final String PREVIOUS_VARIABLE_ACTION = "abbrev_previous_var"; + private static final String STOP_COMPLETION_ACTION = "abbrev_stop_var_completion"; + /** Abbreviation accounting string. Here the characters forming * abbreviation are stored. */ @@ -64,6 +80,19 @@ /** Abbreviation map */ private HashMap abbrevMap; + /** Abreviation variables for the abbreviation being expanded. */ + private ArrayList abbrevVars = new ArrayList(); + + /** Holds which variables is being handled now. -1 means none. */ + private int abbrevVarsCurrentIndex = -1; + + /** Indicates where the caret should be positioned after the variable + * expansion is finished. */ + private Position caretPosition = null; + + /** Flag that indicates if in the variable expansion phase. */ + private boolean expandingVariables; + public Abbrev(EditorUI editorUI, boolean checkDocText, boolean checkTextDelimiter) { this.editorUI = editorUI; this.checkDocText = checkDocText; @@ -230,17 +259,110 @@ if (expansion instanceof String) { // expand to string BaseDocument doc = editorUI.getDocument(); String ins = (String)expansion; - int offset = ins.indexOf('|'); - if (offset >= 0) { - if (offset > 0) doc.insertString(dotPos, ins.substring(0, offset), null); - if (offset+1 < ins.length()) doc.insertString(dotPos + offset, - ins.substring(offset + 1), null); - Caret caret = editorUI.getComponent().getCaret(); - caret.setDot(dotPos + offset); - } else { - doc.insertString(dotPos, ins, null); + + // Resetting the variables. + abbrevVars.clear(); + abbrevVarsCurrentIndex = -1; + AbbrevVariable av = null; + ArrayList varPositions = null; + int count = 0; + int insertionStart = 0; + int charsInserted = 0; + caretPosition = null; + try { + // matches everything in the form ${varname} or | + RE varsRE = new RE("([$][{].*?[}])|[|]"); + boolean matched = varsRE.match(ins); + while (matched) { + String wrappedVarName = varsRE.getParen(0); + String varName = null; + int varEnd = varsRE.getParenEnd(0); + int varStart = varEnd - wrappedVarName.length(); + + // Insert up to the special variable or cursor position. + if (wrappedVarName.equals("|")) { + // save the position where the cursor should be after + // all variables are replaced. + doc.insertString(dotPos + charsInserted, + ins.substring(insertionStart, varStart), + null); + charsInserted += varStart - insertionStart; + varName = ins.substring(varStart, varEnd); + caretPosition = doc.createPosition(dotPos + charsInserted - 1); + insertionStart = varEnd; + } else { + // insert the expansion up to the point where the variable was found + // and save the variable position. The variable name will be + // stripped of ${}. + varName = wrappedVarName.substring(2, wrappedVarName.length() - 1); + String strToInsert = ins.substring(insertionStart, varStart) + varName; + doc.insertString(dotPos + charsInserted, + strToInsert, + null); + charsInserted += strToInsert.length(); + insertionStart = varEnd; + + av = new AbbrevVariable(); + av.setName(varName); + int varIndex = abbrevVars.indexOf(av); + + if (varIndex == -1) { + varPositions = new ArrayList(); + av.setPositions(varPositions); + abbrevVars.add(av); + } else { + av = null; + av = (AbbrevVariable) abbrevVars.get(varIndex); + varPositions = av.getPositions(); + } + + Position position = doc.createPosition(dotPos + varStart - count * 3); + varPositions.add(position); + } + + matched = varsRE.match(ins, varStart + varName.length()); + count++; + } + + // Insert the rest of the expansion. + if (insertionStart + 1 < ins.length()) { + doc.insertString(dotPos + charsInserted, ins.substring(insertionStart), null); + charsInserted += (ins.length() - insertionStart); + } + + } catch (RESyntaxException e) { + // This should never happen. } + + // Only reformat the code if it is a multiline abbreviation. + if (ins.indexOf("\n") != -1) { + Formatter formatter = doc.getFormatter(); + formatter.reformat(doc, dotPos, dotPos + charsInserted); + } + expanded = true; + + if (abbrevVars.size() > 0) { + abbrevVarsCurrentIndex = 0; + selectVariableText(); + + editorUI.getComponent().getActionMap().put(NEXT_VARIABLE_ACTION, new AbbreviationCompletionAction(NEXT_VARIABLE)); + editorUI.getComponent().getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_TAB, 0), (Object)NEXT_VARIABLE_ACTION); + + editorUI.getComponent().getActionMap().put(PREVIOUS_VARIABLE_ACTION, new AbbreviationCompletionAction(PREVIOUS_VARIABLE)); + editorUI.getComponent().getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_TAB, InputEvent.SHIFT_DOWN_MASK), (Object)PREVIOUS_VARIABLE_ACTION); + + editorUI.getComponent().getActionMap().put(STOP_COMPLETION_ACTION, new AbbreviationCompletionAction(STOP_COMPLETION)); + editorUI.getComponent().getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), (Object)STOP_COMPLETION_ACTION); + expandingVariables = true; + } else { + // If there was a | in the expansion then position the caret in + // the right place. + if (caretPosition != null) { + Caret caret = editorUI.getComponent().getCaret(); + caret.setDot(caretPosition.getOffset() + 1); + } + } } else if (expansion instanceof Action) { ((Action)expansion).actionPerformed(evt); expanded = true; @@ -299,7 +421,8 @@ throws BadLocationException { boolean doInsert = true; String expandStr = getExpandString(typedChar); - if (expandStr != null) { // should expand + // Prevent expanding while working with the variables. + if (expandStr != null && ! expandingVariables) { // should expand doInsert = false; expandString(typedChar, expandStr, evt); } else { @@ -317,4 +440,117 @@ } } + + private void stopAbbreviationCompletion() { + expandingVariables = false; + + // Remove the special keys created for variable expansion. + editorUI.getComponent().getActionMap().remove(NEXT_VARIABLE_ACTION); + editorUI.getComponent().getInputMap().remove(KeyStroke.getKeyStroke(KeyEvent.VK_TAB, 0)); + editorUI.getComponent().getActionMap().remove(PREVIOUS_VARIABLE_ACTION); + editorUI.getComponent().getInputMap().remove(KeyStroke.getKeyStroke(KeyEvent.VK_TAB, InputEvent.SHIFT_DOWN_MASK)); + editorUI.getComponent().getActionMap().remove(STOP_COMPLETION_ACTION); + editorUI.getComponent().getInputMap().remove(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0)); + + + Caret caret = editorUI.getComponent().getCaret(); + if (caretPosition != null) { + caret.setDot(caretPosition.getOffset()); + } + expandingVariables = false; + reset(); + } + + private void moveToNextVar() { + replaceAbbreviationVar(); + + abbrevVarsCurrentIndex++; + selectVariableText(); + } + + private void moveToPreviousVar() { + replaceAbbreviationVar(); + + abbrevVarsCurrentIndex--; + selectVariableText(); + } + + private void selectVariableText() { + BaseDocument doc = editorUI.getDocument(); + try { + if (abbrevVarsCurrentIndex < abbrevVars.size() && abbrevVarsCurrentIndex >= 0) { + AbbrevVariable currentVar = (AbbrevVariable) abbrevVars.get(abbrevVarsCurrentIndex); + ArrayList positions = currentVar.getPositions(); + Position pos = (Position) positions.get(0); + + String identifier = Utilities.getIdentifier(doc, pos.getOffset()); + editorUI.getComponent().select(pos.getOffset(), pos.getOffset() + identifier.length()); + } else { + stopAbbreviationCompletion(); + } + } catch (BadLocationException e) { + e.printStackTrace(); + } + } + + private void replaceAbbreviationVar() { + BaseDocument doc = editorUI.getDocument(); + doc.atomicLock(); + try { + AbbrevVariable currentVar = (AbbrevVariable) abbrevVars.get(abbrevVarsCurrentIndex); + ArrayList allDollarPos = (ArrayList)currentVar.getPositions(); + Iterator iterator = allDollarPos.iterator(); + int count = 0; + + // There's at least one position, otherwise we wouldn't be here... + Position position = (Position) iterator.next(); + + // find out what replaced the variable. + String replacement = Utilities.getIdentifier(doc, position.getOffset()); + if (! replacement.equals(currentVar.getName())) { + allDollarPos.set(count, doc.createPosition(position.getOffset() - replacement.length())); + } + count++; + + // now replace all the occurrences with the value that was typed. + while (iterator.hasNext()) { + position = (Position) iterator.next(); + String toReplace = Utilities.getIdentifier(doc, position.getOffset()); + if (! replacement.equals(toReplace)) { + doc.remove(position.getOffset(), toReplace.length()); + doc.insertString(position.getOffset(), replacement, null); + allDollarPos.set(count, doc.createPosition(position.getOffset() - replacement.length())); + } + count++; + } + currentVar.setName(replacement); + + } catch (BadLocationException e) { + e.printStackTrace(); + } finally { + doc.atomicUnlock(); + } + } + + private class AbbreviationCompletionAction extends AbstractAction { + private int action; + + private AbbreviationCompletionAction(int action) { + this.action = action; + } + + public void actionPerformed(java.awt.event.ActionEvent actionEvent) { + switch (action) { + case NEXT_VARIABLE: + moveToNextVar(); + break; + case PREVIOUS_VARIABLE: + moveToPreviousVar(); + break; + case STOP_COMPLETION: + stopAbbreviationCompletion(); + break; + } + } + } }