/****************************************************************************** * Copyright (C) 2011-2013 Fabio Zadrozny 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 * * Contributors: * Fabio Zadrozny <fabiofz@gmail.com> - initial API and implementation * Jonah Graham <jonah@kichwacoders.com> - ongoing maintenance ******************************************************************************/ package org.python.pydev.shared_core.auto_edit; import java.util.Set; import org.eclipse.jface.text.BadLocationException; import org.eclipse.jface.text.DocumentCommand; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.ITextSelection; import org.eclipse.jface.text.TextViewer; import org.eclipse.jface.viewers.ISelection; import org.eclipse.swt.custom.VerifyKeyListener; import org.eclipse.swt.events.VerifyEvent; import org.python.pydev.shared_core.log.Log; import org.python.pydev.shared_core.string.FastStringBuffer; import org.python.pydev.shared_core.string.StringUtils; import org.python.pydev.shared_core.string.TextSelectionUtils; import org.python.pydev.shared_core.structure.Tuple; import org.python.pydev.shared_core.utils.DocCmd; /** * Something similar org.eclipse.jdt.internal.ui.javaeditor.CompilationUnitEditor.BracketInserter (but not too similar). * * @author Fabio Zadrozny */ public class AutoEditStrategyScopeCreationHelper { private int linkOffset; private int linkExitPos; private int linkLen; public static interface IScopeCreatingCharsProvider { int CLOSE_SCOPE = 1; // int CLOSE_SCOPE_IF_SELECTION = 2; int CLOSE_SCOPE_NO = 3; int getCharactersThatCreateScope(String contentType, char c); /** * A list with the start/end sequences that belong to multi-line creation. */ Set<Tuple<String, String>> getMultiLineSequences(); } /** * Creates a handler that will properly treat peers. */ public static VerifyKeyListener createVerifyKeyListener(final TextViewer viewer, final IScopeCreatingCharsProvider provider) { return new VerifyKeyListener() { private final AutoEditStrategyScopeCreationHelper scopeHelper = new AutoEditStrategyScopeCreationHelper(); @Override public void verifyKey(VerifyEvent event) { if (!event.doit || event.character == '\0') { return; } if (viewer != null && viewer.isEditable()) { boolean blockSelection = false; try { blockSelection = viewer.getTextWidget().getBlockSelection(); } catch (Throwable e) { //that's OK (only available in eclipse 3.5) } if (!blockSelection) { ISelection selection = viewer.getSelection(); if (selection instanceof ITextSelection) { //Don't bother in getting the indent prefs from the editor: the default indent prefs are //always global for the settings we want. TextSelectionUtils ps = new TextSelectionUtils(viewer.getDocument(), (ITextSelection) selection); int absoluteCursorOffset = ps.getAbsoluteCursorOffset(); String contentType = AutoEditStrategyHelper.getContentType(ps.getDoc(), absoluteCursorOffset, false); int closeScope = provider.getCharactersThatCreateScope(contentType, event.character); switch (closeScope) { case IScopeCreatingCharsProvider.CLOSE_SCOPE: break; //keep on going in this function case IScopeCreatingCharsProvider.CLOSE_SCOPE_NO: return; case IScopeCreatingCharsProvider.CLOSE_SCOPE_IF_SELECTION: if (ps.getSelLength() == 0) { return; } break; } if (scopeHelper.perform(ps, event.character, viewer, provider)) { event.doit = false; } } } } } }; } /** * @param ps * @param provider */ public boolean perform(TextSelectionUtils ps, final char c, TextViewer viewer, IScopeCreatingCharsProvider provider) { linkOffset = -1; linkExitPos = -1; linkLen = 0; try { IDocument doc = ps.getDoc(); DocCmd docCmd = new DocCmd(ps.getAbsoluteCursorOffset(), ps.getSelLength(), Character.toString(c)); if (!handleScopeCreationChar(doc, docCmd, ps, provider, c)) { return false; //not handled } if (linkOffset == -1 || linkExitPos == -1) { return true; //it was handled (without the link) } if (viewer != null) { viewer.setSelectedRange(linkOffset, linkLen); } } catch (Exception e) { Log.log(e); } return true; } /** * Called right after a ' or " * @param provider * * @return false if we should leave the handling to the auto-indent and true if it handled things properly here. */ private boolean handleScopeCreationChar(IDocument document, DocumentCommand command, TextSelectionUtils ps, IScopeCreatingCharsProvider provider, char c) throws BadLocationException { int offset = ps.getAbsoluteCursorOffset(); Set<Tuple<String, String>> multiLineSequences = provider.getMultiLineSequences(); Tuple<String, String> foundMultiLine = null; for (Tuple<String, String> tuple : multiLineSequences) { if (tuple.o1.length() > 0 && tuple.o1.charAt(0) == c) { foundMultiLine = tuple; break; } } if (command.length > 0) { String selectedText = ps.getSelectedText(); if (selectedText.indexOf('\r') != -1 || selectedText.indexOf('\n') != -1) { if (foundMultiLine != null) { //we have a new line in the selection FastStringBuffer buf = new FastStringBuffer(selectedText.length() + 10); buf.append(foundMultiLine.o1); buf.append(selectedText); buf.append(foundMultiLine.o2); document.replace(offset, ps.getSelLength(), buf.toString()); linkOffset = offset + foundMultiLine.o1.length(); linkLen = selectedText.length(); linkExitPos = linkOffset + linkLen + foundMultiLine.o2.length(); } else { // Not handling multi-line if there's no way to create it at this time. return false; } } else { document.replace(offset, ps.getSelLength(), getReplacement(command, foundMultiLine, selectedText)); linkOffset = offset + 1; linkLen = selectedText.length(); linkExitPos = linkOffset + linkLen + 1; } return true; } try { char nextChar = ps.getCharAtCurrentOffset(); if (Character.isJavaIdentifierPart(nextChar)) { //we're just before a word (don't try to do anything in this case) //e.g. |var (| is cursor position) return false; } } catch (BadLocationException e) { } String cursorLineContents = ps.getCursorLineContents(); if (cursorLineContents.indexOf(c) == -1) { document.replace(offset, ps.getSelLength(), getReplacement(command, foundMultiLine, "")); linkOffset = offset + 1; linkLen = 0; linkExitPos = linkOffset + linkLen + 1; return true; } else if (foundMultiLine != null && command.text.length() == 1) { int length = foundMultiLine.o1.length(); if (length > 1) { if (foundMultiLine.o1.charAt(length - 1) == command.text.charAt(0)) { //if the last char of the multiline is equal to the char being added IDocument doc = ps.getDoc(); int currOffset = ps.getAbsoluteCursorOffset(); String string = doc.get(currOffset - (length - 1), length - 1); //Check if the previous chars in the document match the start of the multiline (without the last char as that's what being added) //If all that matches: close the scope automatically. if (string.equals(foundMultiLine.o1.substring(0, length - 1))) { FastStringBuffer buf = new FastStringBuffer(10); buf.append(c); buf.append(foundMultiLine.o2); document.replace(offset, 0, buf.toString()); linkOffset = offset + 1; linkLen = 0; linkExitPos = linkOffset + linkLen + foundMultiLine.o2.length(); return true; } } } } boolean balanced = isLiteralBalanced(cursorLineContents); Tuple<String, String> beforeAndAfterMatchingChars = ps.getBeforeAndAfterMatchingChars(c); int matchesBefore = beforeAndAfterMatchingChars.o1.length(); int matchesAfter = beforeAndAfterMatchingChars.o2.length(); boolean hasMatchesBefore = matchesBefore != 0; boolean hasMatchesAfter = matchesAfter != 0; if (!hasMatchesBefore && !hasMatchesAfter) { //if it's not balanced, this char would be the closing char. if (balanced) { document.replace(offset, ps.getSelLength(), getReplacement(command, foundMultiLine, "")); linkOffset = offset + 1; linkLen = 0; linkExitPos = linkOffset + linkLen + 1; return true; } } else { //we're right after or before a " or ' return false; } return false; } private String getReplacement(DocumentCommand command, Tuple<String, String> found, String selectedText) { String replacement; if (found != null && found.o1.equals(command.text)) { replacement = found.o1 + selectedText + found.o2; } else { if (command.text.length() == 1) { char peer; char char0 = command.text.charAt(0); try { peer = StringUtils.getPeer(char0); } catch (Exception e) { peer = char0; } replacement = char0 + selectedText + peer; } else { replacement = command.text + selectedText + command.text; } } return replacement; } /** * @return true if the passed string has balanced ' and " */ private boolean isLiteralBalanced(String cursorLineContents) { return true; } /** * In default namespace (used for testing) */ int getLinkLen() { return linkLen; } int getLinkExitPos() { return linkExitPos; } int getLinkOffset() { return linkOffset; } }