package org.epic.perleditor.editors;
import org.eclipse.core.runtime.*;
import org.eclipse.jface.text.*;
import org.eclipse.jface.text.source.ISourceViewer;
import org.eclipse.swt.custom.StyledText;
import org.eclipse.swt.custom.VerifyKeyListener;
import org.eclipse.swt.events.VerifyEvent;
import org.eclipse.swt.graphics.Point;
import org.epic.perleditor.PerlEditorPlugin;
/**
* Implements the "smart typing" functionality (automatically closing
* quotes and brackets).
*
* @author jploski
*/
class PerlBracketInserter implements VerifyKeyListener
{
private static final char NON_BRACKET = '\u0000';
private final ILog log;
private boolean closeAngularBrackets;
private boolean closeBraces;
private boolean closeBrackets;
private boolean closeParens;
private boolean closeDoubleQuotes;
private boolean closeSingleQuotes;
private ISourceViewer viewer;
public PerlBracketInserter(ILog log)
{
this.log = log;
}
public boolean isEnabled()
{
return
viewer != null &&
(closeAngularBrackets ||
closeBraces ||
closeBrackets ||
closeDoubleQuotes ||
closeSingleQuotes ||
closeParens);
}
public void setCloseAngularBracketsEnabled(boolean enabled)
{
closeAngularBrackets = enabled;
}
public void setCloseBracesEnabled(boolean enabled)
{
closeBraces = enabled;
}
public void setCloseBracketsEnabled(boolean enabled)
{
closeBrackets = enabled;
}
public void setCloseDoubleQuotesEnabled(boolean enabled)
{
closeDoubleQuotes = enabled;
}
public void setCloseSingleQuotesEnabled(boolean enabled)
{
closeSingleQuotes = enabled;
}
public void setCloseParensEnabled(boolean enabled)
{
closeParens = enabled;
}
public void setViewer(ISourceViewer viewer)
{
this.viewer = viewer;
}
public void verifyKey(VerifyEvent event)
{
if (!event.doit || !isEnabled()) return;
char closingChar = getClosingChar(event.character);
if (closingChar == NON_BRACKET) return;
event.doit = processBracketKeyStroke(
viewer.getDocument(),
viewer.getSelectedRange(),
event.character,
closingChar);
}
/**
* @return if <code>c</code> is one of the bracket characters for
* which bracket insertion is enabled, the correspnding
* closing bracket character; otherwise NON_BRACKET
*/
private char getClosingChar(char c)
{
switch (c)
{
case ')':
case '(':
return closeParens ? ')' : NON_BRACKET;
case '>':
case '<':
return closeAngularBrackets ? '>' : NON_BRACKET;
case '}':
case '{':
return closeBraces ? '}' : NON_BRACKET;
case ']':
case '[':
return closeBrackets ? ']' : NON_BRACKET;
case '\'':
return closeSingleQuotes ? '\'' : NON_BRACKET;
case '\"':
return closeDoubleQuotes ? '"' : NON_BRACKET;
default:
return NON_BRACKET;
}
}
/**
* @return true if the given character inserted at the given offset
* in the document would act as a "closing" character;
* false otherwise
*/
private boolean isClosingChar(IDocument doc, int offset, char c)
{
if (c == '}' || c == ']' || c == '>' || c == ')') return true; // easy
else if (offset == 0) return false; // easy
else
{
try
{
// A quote is a closing char when inserted to terminate a string literal,
// otherwise it is an opening char:
String partitionType = PartitionTypes.getPerlPartition(doc, offset-1).getType();
return PartitionTypes.LITERAL1.equals(partitionType) ||
PartitionTypes.LITERAL2.equals(partitionType);
}
catch (BadLocationException e)
{
logBadLocationException(e);
return false;
}
}
}
/**
* BadLocationExceptions should never occur in PerlBracketInserter.
*/
private void logBadLocationException(BadLocationException e)
{
log.log(
new Status(Status.ERROR,
PerlEditorPlugin.getPluginId(),
IStatus.OK,
"Unexpected exception; report it as a bug " +
"in plug-in " + PerlEditorPlugin.getPluginId(),
e));
}
/**
* @param doc document to be modified in result of the key stroke
* @param selection selection in the document at the time of the key stroke
* (x = offset, y = length) or caret position if there was
* no selection (x, y = 0)
* @param keystrokeChar character entered by the user
* @param closingChar the corresponding "closing" character
* @return true if the key stroke event should be processed,
* false if it should be discarded
*/
private boolean processBracketKeyStroke(
IDocument doc,
Point selection,
char keystrokeChar,
char closingChar)
{
final int offset = selection.x;
final int length = selection.y;
try
{
// Duplication of apostrophes in a comment/POD is undesirable:
if (keystrokeChar == '\'' && offset > 0)
{
String partitionType = PartitionTypes.getPerlPartition(doc, offset-1).getType();
if (PartitionTypes.POD.equals(partitionType) ||
PartitionTypes.COMMENT.equals(partitionType)) return true;
}
if (isClosingChar(doc, offset, keystrokeChar))
{
// The user has just typed a closing char
if (offset + length < doc.getLength() &&
doc.getChar(offset + length) == closingChar)
{
// There's already a closing char in front of us, so skip it
skipChar();
return false;
}
}
else
{
// The user has just typed an opening char
if (offset + length < doc.getLength() &&
doc.getChar(offset + length) == keystrokeChar)
{
// There's already an opening char in front of us, so skip it
skipChar();
return false;
}
else
{
// Auto-insert opening char before and closing char after selection...
doc.replace(offset, 0, String.valueOf(new char[] { keystrokeChar }));
doc.replace(offset + length + 1, 0, String.valueOf(new char[] { closingChar }));
// ...and position the caret after the entered char
skipChar();
return false;
}
}
return true;
}
catch (BadLocationException e)
{
logBadLocationException(e);
return true;
}
}
private void skipChar()
{
StyledText text = viewer.getTextWidget();
text.setCaretOffset(text.getCaretOffset()+1);
}
}