blob: c40e24cd3f27f2468795f04b2a1b8480446fbb63 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2005, 2016 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
*******************************************************************************/
package org.eclipse.dltk.python.internal.ui.text;
import java.util.regex.Pattern;
import org.eclipse.dltk.core.DLTKCore;
import org.eclipse.dltk.python.ui.text.IPythonPartitions;
import org.eclipse.dltk.ui.PreferenceConstants;
import org.eclipse.dltk.ui.text.ScriptDefaultIndentLineAutoEditStrategy;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.Document;
import org.eclipse.jface.text.DocumentCommand;
import org.eclipse.jface.text.DocumentRewriteSession;
import org.eclipse.jface.text.DocumentRewriteSessionType;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.ITypedRegion;
import org.eclipse.jface.text.TextUtilities;
import org.eclipse.jface.text.rules.FastPartitioner;
public class PythonAutoEditStrategy extends
ScriptDefaultIndentLineAutoEditStrategy {
IPreferenceStore fStore;
String fPartitioning;
final int maxCharsAway = 100;
public PythonAutoEditStrategy(IPreferenceStore store, String part) {
super(store);
fStore = store;
fPartitioning = part;
}
/*
* possible prefereces:
*
* general indent on/off smart tab key behaviour autoclose "strings",
* 'strings' autoclose {brackets}, [], () smart paste
*/
private boolean isSmartTab() {
return fStore.getBoolean(PreferenceConstants.EDITOR_SMART_TAB);
}
private boolean isSmartMode() {
return fStore.getBoolean(PreferenceConstants.EDITOR_SMART_INDENT);
}
private boolean closeBrackets() {
return fStore.getBoolean(PreferenceConstants.EDITOR_CLOSE_BRACKETS);
}
private boolean isSmartPaste() {
return fStore.getBoolean(PreferenceConstants.EDITOR_SMART_PASTE);
}
private boolean closeStrings() {
return fStore.getBoolean(PreferenceConstants.EDITOR_CLOSE_STRINGS);
}
/**
* Returns the leading whitespaces and tabs.
*
* @param document
* - the document being parsed
* @param line
* - the line being searched
* @return the leading whitespace
* @throws BadLocationException
* in case <code>line</code> is invalid in the document
*/
private String getLineIndent(IDocument document, int line)
throws BadLocationException {
if (line > -1) {
int start = document.getLineOffset(line);
int end = start + document.getLineLength(line); // was - 1
int whiteend = findEndOfWhiteSpace(document, start, end);
return document.get(start, whiteend - start);
}
return ""; //$NON-NLS-1$
}
/**
* Returns the leading whitespaces and tabs.
*
* @param line
* - the line being searched
* @return the leading whitespace
*/
public String getLineIndent(String line) {
int end = line.length();
int whiteend = end;
int offset = 0;
while (offset < end) {
char c = line.charAt(offset);
if (c != ' ' && c != '\t') {
whiteend = offset;
break;
}
offset++;
}
return line.substring(0, whiteend);
}
/**
* Find line with number <=line, that is not empty and is not a comment line
* starting with #
*
* @param d
* the document to search in
* @param line
* number of starting line
* @return number of code line or -1 if no such line found
* @throws BadLocationException
*/
private int getLastNonEmptyLine(IDocument d, int line)
throws BadLocationException {
int res = line;
while (res > -1) {
String str = getDocumentLine(d, res).trim();
if ((!str.startsWith("#")) && str.length() > 0)
return res;
res--;
}
return res;
}
/**
* Fetched line from document
*
* @param d
* the document
* @param line
* number of req. line
* @return string with line
* @throws BadLocationException
* if <b>line</b> is not correct line number
*/
public String getDocumentLine(IDocument d, int line)
throws BadLocationException {
int start = d.getLineOffset(line);
int length = d.getLineLength(line);
return d.get(start, length);
}
/**
* Get partition type covering offset
*
* @param d
* @param offset
* @return
* @throws BadLocationException
*/
private String getRegionType(IDocument d, int offset)
throws BadLocationException {
int p = ((offset == d.getLength()) ? offset - 1 : offset);
ITypedRegion region = TextUtilities.getPartition(d, fPartitioning, p,
true);
return region.getType();
}
/**
* Searchs an pair from offset, forward of backwards. Can skip strings and
* comments (uses python partitioning). Doesn't goes more maxCharsAway chars
* away from offset.
*
* @param d
* @param offset
* @param forward
* @param opening
* @param closing
* @param skipCommentLines
* @param skipStrings
* @return offset of pair, or -1 if not found
* @throws BadLocationException
*/
private int searchPair(IDocument d, int offset, boolean forward,
char opening, char closing, boolean skipCommentLines,
boolean skipStrings) throws BadLocationException {
int deep = 0;
int i = offset;
if (forward) {
while (i < d.getLength()) {
ITypedRegion region = TextUtilities.getPartition(d,
fPartitioning, i, true);
if (region.getType() == IPythonPartitions.PYTHON_COMMENT
&& skipCommentLines) {
i = region.getOffset() + region.getLength();
continue;
}
if (region.getType() == IPythonPartitions.PYTHON_STRING
&& skipStrings) {
i = region.getOffset() + region.getLength();
continue;
}
char c = d.getChar(i);
if (c == opening)
deep++;
if (c == closing) {
if (deep == 0)
return i;
deep--;
}
i++;
if (i - offset > maxCharsAway)
return -1;
}
} else {
while (i >= 0) {
ITypedRegion region = TextUtilities.getPartition(d,
fPartitioning, i, true);
if (region.getType() == IPythonPartitions.PYTHON_COMMENT
&& skipCommentLines) {
i = region.getOffset() - 1;
continue;
}
if (region.getType() == IPythonPartitions.PYTHON_STRING
&& skipStrings) {
i = region.getOffset() - 1;
continue;
}
char c = d.getChar(i);
if (c == closing)
deep++;
if (c == opening) {
if (deep == 0)
return i;
deep--;
}
i--;
if (offset - i > maxCharsAway)
return -1;
}
}
return -1;
}
/**
* Finds line < <b>line</b>, such the indent of it is less than of
* <b>line</b>
*
* @param d
* the document to search in
* @param line
* appropriate line number
* @return if found, number < <b>line</b>, else <b>line</b>
*/
private int findIndentStart(IDocument d, int line)
throws BadLocationException {
String curIndent = getLineIndent(d, line);
int curIndentLength = getPhysicalLength(curIndent);
int cur = line - 1;
while (cur >= 0) {
String curLine = getDocumentLine(d, cur);
String ind = getLineIndent(d, cur);
if ((!curLine.trim().startsWith("#"))
&& getPhysicalLength(ind) < curIndentLength)
return cur;
cur--;
}
return line;
}
/**
* Calculates real length of string. So any char except \t has length 1, \t
* has length 8.
*
* @param str
* string to process
* @return length
*/
public int getPhysicalLength(String str) {
int res = 0;
for (int i = 0; i < str.length(); i++) {
if (str.charAt(i) == '\t')
res += 8; // in python \t always equals eight spaces!
else
res++;
}
return res;
}
/**
* Return pair to brace. Ex. '(' for ')', e.t.c.
*
* @param b
* input brace
* @return peer brace
*/
private char getBracePair(char b) {
switch (b) {
case '(':
return ')';
case ')':
return '(';
case '[':
return ']';
case ']':
return '[';
case '{':
return '}';
case '}':
return '{';
case '\"':
return '\"';
case '\'':
return '\'';
}
return b;
}
/**
* Installs a partitioner with <code>document</code>.
*
* @param document
* the document
*/
private static void installStuff(Document document) {
String[] types = new String[] { IPythonPartitions.PYTHON_STRING,
IPythonPartitions.PYTHON_COMMENT,
IDocument.DEFAULT_CONTENT_TYPE };
FastPartitioner partitioner = new FastPartitioner(
new PythonPartitionScanner(), types);
partitioner.connect(document);
document.setDocumentPartitioner(IPythonPartitions.PYTHON_PARTITIONING,
partitioner);
}
/**
* Removes partitioner with <code>document</code>.
*
* @param document
* the document
*/
private static void removeStuff(Document document) {
document.setDocumentPartitioner(IPythonPartitions.PYTHON_PARTITIONING,
null);
}
/**
* STRATEGIES
*/
/**
* Main indenting algorithm. Needs correct partitioning to work.
*
* @param d
* the processed document
* @param line
* line, indenting of which we wanna know
* @param newLine
* if <b>line</b> is new line, so we have pressed enter-key, and
* need indentation for new line
* @param offset
* position, where we have jumped to new line
* @return correct indent for line, or null if we should use "default"
* indent. So calling side should determine, change indent or not,
* or may be use indent of previous line.
* @throws BadLocationException
*/
private String calcLineIndent(IDocument d, int line, boolean newLine,
int offset) throws BadLocationException {
boolean isDocumentEnd = (offset == d.getLength());
// STRINGS
if (newLine) {
// if we wrap string
if (getRegionType(d, offset) == IPythonPartitions.PYTHON_STRING) {
int realLine = d.getLineOfOffset(offset);
String curIndent = getLineIndent(d, realLine);
// if we just closed string
if (d.getChar(offset - 1) != '"') {
// if we are fully in string
if (getRegionType(d, d.getLineOffset(realLine)) == IPythonPartitions.PYTHON_STRING) {
return curIndent;
}
// if we first time wrap string
return curIndent + getIndent();
}
}
} else {
// don't correct strings
if (getRegionType(d, d.getLineOffset(line)) == IPythonPartitions.PYTHON_STRING) {
return getLineIndent(d, line); // notice, that we don't use null
}
}
// LINE JOINING
if (newLine) {
int realLine = d.getLineOfOffset(offset);
if (line > 0) {
String previousLine = "";
if (realLine == line - 1) {
int start = d.getLineOffset(realLine);
previousLine = d.get(start, offset - start);
} else
previousLine = getDocumentLine(d, line - 1);
if (previousLine.trim().endsWith("\\")) {
String prePreviousLine = getDocumentLine(d, line - 2);
if (prePreviousLine.trim().endsWith("\\"))
return getLineIndent(d, line - 1);
return getLineIndent(d, line - 1) + getIndent()
+ getIndent();
}
if (line > 1) {
String prePreviousLine = getDocumentLine(d, line - 2);
if (prePreviousLine.trim().endsWith("\\")) {
// find start
int t = line - 2;
while (t > 0
&& getDocumentLine(d, t - 1).trim().endsWith(
"\\")) {
t--;
}
return getLineIndent(d, t);
}
}
}
} else {
/*
* If this line is explicitly joined: If the previous line was also
* joined, line it up with that one, otherwise add two 'shiftwidth'
*/
if (line > 0) {
String previousLine = getDocumentLine(d, line - 1);
if (previousLine.trim().endsWith("\\")) {
if (line > 1) {
String prePreviousLine = getDocumentLine(d, line - 2);
if (prePreviousLine.trim().endsWith("\\"))
return getLineIndent(d, line - 1);
}
return getLineIndent(d, line - 1) + getIndent()
+ getIndent();
}
}
}
// Search backwards for the previous non-empty line.
int lastNonEmptyLine = getLastNonEmptyLine(d, line - 1);
if (lastNonEmptyLine < 0) {
// This is the first non-empty line, use zero indent.
return "";
}
// first check, if we not inside string, if yes, jump to start
ITypedRegion region = TextUtilities.getPartition(d, fPartitioning,
d.getLineOffset(lastNonEmptyLine), true);
if (region.getType() == IPythonPartitions.PYTHON_STRING) {
lastNonEmptyLine = d.getLineOfOffset(region.getOffset());
}
// If the previous line is inside parenthesis, use the indent of the
// starting line.
int plnumstart;
String previousLineIndent = "";
int pairOffset = searchPair(d, d.getLineOffset(lastNonEmptyLine),
false, '(', ')', true, true);
if (pairOffset >= 0) {
plnumstart = d.getLineOfOffset(pairOffset);
previousLineIndent = getLineIndent(d, plnumstart);
} else {
plnumstart = lastNonEmptyLine;
previousLineIndent = getLineIndent(d, lastNonEmptyLine);
}
/*
* When inside parenthesis: If at the first line below the parenthesis
* add two 'shiftwidth', otherwise same as previous line. i = (a + b +
* c)
*/
int p = searchPair(d, offset - 1, false, '(', ')', true, true);
if (p >= 0) {
if (d.getLineOfOffset(p) == lastNonEmptyLine) {
// When the start is inside parenthesis, only indent one
// 'shiftwidth'.
int pp = searchPair(d, p, false, '(', ')', true, true);
if (pp >= 0)
return getLineIndent(d, lastNonEmptyLine) + getIndent();
return getLineIndent(d, lastNonEmptyLine) + getIndent()
+ getIndent();
}
if (d.getLineOfOffset(p) == plnumstart) {
return getLineIndent(d, lastNonEmptyLine);
}
if (d.getLineOfOffset(p) == line && !newLine)
return null;
return previousLineIndent;
}
// Get the line and remove a trailing comment.
String pline = "";
if (lastNonEmptyLine == line - 1 && newLine) {
pline = d.get(d.getLineOffset(line - 1),
offset - d.getLineOffset(line - 1));
} else {
pline = getDocumentLine(d, lastNonEmptyLine);
}
int plineLen = pline.length();
int i;
for (i = 0; i < plineLen; i++) {
if (pline.charAt(i) == '#') {
pline = pline.substring(0, i);
break;
}
}
String plineTrimmed = pline.trim();
try {// If the current line begins with a keyword that lines up with
// "try"
String curLine = "";
if (lastNonEmptyLine == line - 1 && newLine) {
curLine = d.get(offset,
d.getLineLength(line - 1) + d.getLineOffset(line - 1)
- offset);
} else {
curLine = getDocumentLine(d, line).trim();
}
if (curLine.startsWith("except") || curLine.startsWith("finally")) {
int lnum = line - 1;
while (lnum >= 0) {
String temp = getDocumentLine(d, lnum).trim();
if (temp.startsWith("try") || temp.startsWith("except")) {
String ind = getLineIndent(d, lnum);
return ind;
}
lnum--;
}
// no mathing "try"
// System.out.println ("No matching 'try'!");
return null;
}
// If the current line begins with a header keyword, dedent
if (curLine.startsWith("elif") || curLine.startsWith("else")) {
// Unless the previous line was a one-liner
String temp = getDocumentLine(d, lastNonEmptyLine).trim();
if (temp.startsWith("for") || temp.startsWith("if")
|| temp.startsWith("try") || temp.startsWith("while")) {
return previousLineIndent;
}
int sline = findIndentStart(d, lastNonEmptyLine);
String reqIndent = getLineIndent(d, sline);
return reqIndent;
}
} catch (BadLocationException e) {
// do nothing, we just don't have current line
}
// If the previous line was a stop-execution statement...
String regex = "^\\s*(break|continue|raise|pass|return)(\\s+.*$|$)";
if (Pattern.matches(regex, plineTrimmed)) {
// find indent
int sline = findIndentStart(d, lastNonEmptyLine);
String reqIndent = getLineIndent(d, sline);
if (newLine
|| isDocumentEnd
|| (getPhysicalLength(getLineIndent(d, line)) > getPhysicalLength(reqIndent))) {
return reqIndent;
}
// trust the user
return null;
}
// If the previous line ended with a colon, indent this line
if (plineTrimmed.endsWith(":"))
return previousLineIndent + getIndent();
if (pairOffset >= 0 && newLine) {
return previousLineIndent;
}
// after-string
int prevLine = getLastNonEmptyLine(d, line - 1);
if (getRegionType(d, d.getLineOffset(prevLine)) == IPythonPartitions.PYTHON_STRING)
return previousLineIndent;
return null;
}
/**
* If we have pressed ":" for example, than we need to reindent line. This
* function changes document and sets correct indent for current line.
*
* @param d
* @param c
*/
private void reindent(IDocument d, DocumentCommand c) {
try {
if (getRegionType(d, c.offset) != IDocument.DEFAULT_CONTENT_TYPE)
return;
int line = d.getLineOfOffset(c.offset);
String newIndent = calcLineIndent(d, line, false, c.offset);
if (newIndent == null)
return;
String curIndent = getLineIndent(d, line);
if (getPhysicalLength(curIndent) < getPhysicalLength(newIndent))
return;
d.replace(d.getLineOffset(line), curIndent.length(), newIndent);
c.offset += (newIndent.length() - curIndent.length());
} catch (BadLocationException e) {
}
}
/**
* Processes command in work with brackets, strings, etc
*
* @param d
* @param c
*/
private void autoClose(IDocument d, DocumentCommand c) {
if (c.offset == -1)
return;
try {
if (d.getChar(c.offset - 1) == '\\')
return;
} catch (BadLocationException e1) {
}
if ('\"' == c.text.charAt(0) && !closeStrings())
return;
if ('\'' == c.text.charAt(0) && !closeStrings())
return;
if (!closeBrackets()
&& ('[' == c.text.charAt(0) || '(' == c.text.charAt(0) || '{' == c.text
.charAt(0)))
return;
try {
switch (c.text.charAt(0)) {
case '\"':
case '\'':
// if we close existing quote, do nothing
if ('\"' == c.text.charAt(0) && c.offset > 0
&& "\"".equals(d.get(c.offset - 1, 1)))
return;
if ('\'' == c.text.charAt(0) && c.offset > 0
&& "\'".equals(d.get(c.offset - 1, 1)))
return;
if (c.offset != d.getLength()
&& c.text.charAt(0) == d.get(c.offset, 1).charAt(0))
c.text = "";
else {
c.text += c.text;
c.length = 0;
}
c.shiftsCaret = false;
c.caretOffset = c.offset + 1;
break;
case '(':
case '{':
case '[':
// check partition
if (getRegionType(d, c.offset) != IDocument.DEFAULT_CONTENT_TYPE)
return;
if (c.offset != d.getLength()
&& c.text.charAt(0) == d.get(c.offset, 1).charAt(0))
return;
try { // in class closing
String regex = "^\\s*class\\s+.*";
String regex2 = ".*\\(.*\\).*";
int start = d.getLineOffset(d.getLineOfOffset(c.offset));
String curLine = d.get(start, c.offset - start);
if (Pattern.matches(regex, curLine)
&& !Pattern.matches(regex2, curLine)) {
c.text = "():";
c.shiftsCaret = false;
c.caretOffset = c.offset + 1;
return;
}
} catch (BadLocationException e) {
}
// add closing peer
c.text = c.text + getBracePair(c.text.charAt(0));
c.length = 0;
c.shiftsCaret = false;
c.caretOffset = c.offset + 1;
break;
case '}':
case ']':
case ')':
// check partition
if (getRegionType(d, c.offset) != IDocument.DEFAULT_CONTENT_TYPE)
return;
if (!closeBrackets())
return;
// if we already have bracket we should jump over it
if (c.offset != d.getLength()
&& c.text.charAt(0) == d.get(c.offset, 1).charAt(0)) {
c.text = "";
c.shiftsCaret = false;
c.caretOffset = c.offset + 1;
return;
}
break;
}
} catch (BadLocationException e) {
e.printStackTrace();
}
}
/**
* When we have pressed \t, sometimes we wanna not to tabulate, and jump to
* correct line start position.
*
* @param d
* @param c
* @return
*/
private boolean smartIndentJump(IDocument d, DocumentCommand c) {
if (c.offset == -1 || d.getLength() == 0)
return false;
try {
// int p = (c.offset == d.getLength() ? c.offset - 1 : c.offset);
int line = d.getLineOfOffset(c.offset);
int start = d.getLineOffset(line);
// calculate indentation of this line
String resultIndent = calcLineIndent(d, line, false, start);
String currentIndent = getLineIndent(d, line);
if (resultIndent == null) { // we should save indentation
String curLine = getDocumentLine(d, line);
if (curLine.trim().length() > 0) // if indentation is "real",
// use it
resultIndent = currentIndent;
else {
// get current block level
int pl = getLastNonEmptyLine(d, line - 1); // find last code
// line
if (pl >= 0) {
String plStr = getDocumentLine(d, pl).trim();
// simple indent-guess strategy
String regex = "^\\s*(break|continue|raise|pass|return)(\\s+.*$|$)";
if (plStr.endsWith(":"))
resultIndent = getLineIndent(plStr) + getIndent();
else if (Pattern.matches(regex, plStr)) {
// find indent
int sline = findIndentStart(d, pl);
resultIndent = getLineIndent(d, sline);
} else
resultIndent = getLineIndent(d, pl);
} else
return false; // no indent is applicable, do nothing
}
}
if (c.offset >= start + resultIndent.length())
return false; // we already in the place
if (!currentIndent.startsWith(resultIndent)) { // create indent
c.offset = start;
c.length = currentIndent.length();
c.shiftsCaret = false;
c.text = resultIndent;
c.caretOffset = d.getLineOffset(line) + resultIndent.length();
return true;
}
c.length = 0;
c.shiftsCaret = false;
c.text = "";
c.caretOffset = d.getLineOffset(line) + resultIndent.length();
return true;
} catch (BadLocationException e) {
e.printStackTrace();
}
return false;
}
/**
* Reindents c.text when pasting.
*
* @param d
* @param c
*/
private void smartPaste(IDocument d, DocumentCommand c) {
/*
* We are creating temp document, inserting text as is, and then
* sequentially calling calcLineIndent for each new line.
*/
try {
String content = d.get(0, c.offset) + c.text;
Document temp = new Document(content);
DocumentRewriteSession session = temp
.startRewriteSession(DocumentRewriteSessionType.STRICTLY_SEQUENTIAL);
installStuff(temp);
int offset = c.offset;
int line = temp.getLineOfOffset(offset);
String lastIndent = getLineIndent(temp, line);
int firstLineOffset = temp.getLineOffset(line);
String commonIndent = temp.get(firstLineOffset, c.offset
- firstLineOffset);
line++;
try {
while (getDocumentLine(temp, line).trim().length() == 0)
line++;
offset = temp.getLineOffset(line);
} catch (BadLocationException e) {
// ok, we are inserting only one string...
offset = temp.getLength();
}
while (offset < temp.getLength()) {
// calculate indentation of this line
String resultIndent = calcLineIndent(temp, line, false,
temp.getLineOffset(line));
// change current line offset
String currentIndent = getLineIndent(temp, line);
// String dbg = temp.get ();
if (resultIndent == null) {
resultIndent = commonIndent + currentIndent;
if (getPhysicalLength(resultIndent) > getPhysicalLength(lastIndent))
resultIndent = lastIndent;
}
temp.replace(offset, currentIndent.length(), resultIndent);
String currentLine = getDocumentLine(temp, line);
if (currentLine.trim().length() > 0
&& (!currentLine.trim().startsWith("#")))
lastIndent = resultIndent;
// dbg = temp.get ();
if (temp.getLineOffset(line) + temp.getLineLength(line) == temp
.getLength())
break;
line++;
offset = temp.getLineOffset(line);
}
temp.stopRewriteSession(session);
removeStuff(temp);
c.text = temp.get(c.offset, temp.getLength() - c.offset);
} catch (BadLocationException e) {
e.printStackTrace();
}
}
@Override
public void customizeDocumentCommand(IDocument d, DocumentCommand c) {
if (c.doit == false)
return;
if (c.length == 0 && c.text != null && isLineDelimiter(d, c.text)) {
if (!isSmartMode()) {
super.customizeDocumentCommand(d, c);
return;
}
try {
String indent = calcLineIndent(d,
d.getLineOfOffset(c.offset) + 1, true, c.offset);
if (indent == null)
super.customizeDocumentCommand(d, c);
else {
if (DLTKCore.DEBUG) {
System.err.println("Bug:PTN-9");
}
// if (c.offset - 1 >= 0 &&
// d.getChar(c.offset - 1) != '"' &&
// getRegionType(d, c.offset - 1) ==
// IPythonPartitions.PYTHON_STRING)
// c.text = "\\" + c.text + indent;
// else {
c.text = c.text + indent;
// }
}
} catch (BadLocationException e) {
super.customizeDocumentCommand(d, c);
}
return;
} else {
if (c.length <= 1 && c.text.length() == 1) {
switch (c.text.charAt(0)) {
case ':':
reindent(d, c);
break;
case '\"':
case '\'':
case '(':
case '{':
case '[':
case '}':
case ']':
case ')':
autoClose(d, c);
break;
case '\t':
boolean jumped = false;
if (isSmartTab()) {
jumped = smartIndentJump(d, c);
}
if (!jumped) {
c.text = getIndent();
}
break;
}
} else if (c.text.length() >= 1 && isSmartPaste())
smartPaste(d, c); // no smart backspace for paste
}
}
}