/* * Code borrowed from PyDev * * @author: ptoofani * @author Fabio Zadrozny * Created: June 2004 * License: Common Public License v1.0 */ package org.erlide.ui.util; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.regex.Pattern; import org.eclipse.jface.text.BadLocationException; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.IRegion; import org.eclipse.jface.text.ITextSelection; import org.eclipse.jface.text.Region; import org.eclipse.jface.text.TextSelection; import org.eclipse.jface.text.TextUtilities; import org.eclipse.ui.PlatformUI; import org.eclipse.ui.texteditor.ITextEditor; import org.eclipse.xtext.xbase.lib.Pair; import org.erlide.util.ErlLogger; /** * Used as 'shortcuts' to document and selection settings. * * @author Fabio Zadrozny * @author Parhaum Toofanian */ public class ErlideSelection { private final IDocument doc; private ITextSelection textSelection; /** * Alternate constructor for ErlideSelection. Takes in a text editor from * Eclipse. * * @param textEditor * The text editor operating in Eclipse */ public ErlideSelection(final ITextEditor textEditor) { this(textEditor.getDocumentProvider().getDocument(textEditor.getEditorInput()), (ITextSelection) textEditor.getSelectionProvider().getSelection()); } /** * @param document * the document we are using to make the selection * @param selection * that's the actual selection. It might have an offset and a * number of selected chars */ public ErlideSelection(final IDocument doc, final ITextSelection selection) { this.doc = doc; textSelection = selection; } /** * Creates a selection from a document * * @param doc * the document to be used * @param line * the line (starts at 0) * @param col * the col (starts at 0) */ public ErlideSelection(final IDocument doc, final int line, final int col) { this(doc, line, col, 0); } public ErlideSelection(final IDocument doc, final int line, final int col, final int len) { this.doc = doc; textSelection = new TextSelection(doc, getAbsoluteCursorOffset(line, col), len); } public static int getAbsoluteCursorOffset(final IDocument doc, final int line, final int col) { try { final IRegion offsetR = doc.getLineInformation(line); return offsetR.getOffset() + col; } catch (final Exception e) { throw new RuntimeException(e); } } /** * @param line * 0-based * @param col * 0-based * @return the absolute cursor offset in the contained document */ public int getAbsoluteCursorOffset(final int line, final int col) { return getAbsoluteCursorOffset(doc, line, col); } /** * @param document * the document we are using to make the selection * @param offset * the offset where the selection will happen (0 characters will * be selected) */ public ErlideSelection(final IDocument doc, final int offset) { this.doc = doc; textSelection = new TextSelection(doc, offset, 0); } /** * Changes the selection * * @param absoluteStart * this is the offset of the start of the selection * @param absoluteEnd * this is the offset of the end of the selection */ public void setSelection(final int absoluteStart, final int absoluteEnd) { textSelection = new TextSelection(doc, absoluteStart, absoluteEnd - absoluteStart); } /** * Creates a selection for the document, so that no characters are selected * and the offset is position 0 * * @param doc * the document where we are doing the selection */ public ErlideSelection(final IDocument doc) { this(doc, 0); } /** * In event of partial selection, used to select the full lines involved. */ public void selectCompleteLine() { final IRegion endLine = getEndLine(); final IRegion startLine = getStartLine(); textSelection = new TextSelection(doc, startLine.getOffset(), endLine.getOffset() + endLine.getLength() - startLine.getOffset()); } protected static void beep(final Exception e) { ErlLogger.info(e); PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell().getDisplay() .beep(); } /** * @return the current column that is selected from the cursor. */ public int getCursorColumn() { try { final int absoluteOffset = getAbsoluteCursorOffset(); final IRegion region = doc.getLineInformationOfOffset(absoluteOffset); return absoluteOffset - region.getOffset(); } catch (final BadLocationException e) { throw new RuntimeException(e); } } /** * Gets current line from document. * * @return String line in String form */ public String getLine() { return getLine(getDoc(), getCursorLine()); } /** * Gets line from document. * * @param i * Line number * @return String line in String form */ public String getLine(final int i) { return getLine(getDoc(), i); } /** * Gets line from document. * * @param i * Line number * @return String line in String form */ public static String getLine(final IDocument doc, final int i) { try { return doc.get(doc.getLineInformation(i).getOffset(), doc.getLineInformation(i).getLength()); } catch (final Exception e) { return ""; } } public int getLineOfOffset() { return getLineOfOffset(this.getAbsoluteCursorOffset()); } public int getLineOfOffset(final int offset) { return getLineOfOffset(getDoc(), offset); } /** * @param offset * the offset we want to get the line * @return the line of the passed offset */ public static int getLineOfOffset(final IDocument doc, final int offset) { try { return doc.getLineOfOffset(offset); } catch (final BadLocationException e) { return 0; } } /** * @return the offset of the line where the cursor is */ public int getLineOffset() { return getLineOffset(getCursorLine()); } /** * @return the offset of the specified line */ public int getLineOffset(final int line) { try { return getDoc().getLineInformation(line).getOffset(); } catch (final Exception e) { return 0; } } /** * Deletes a line from the document * * @param i */ public void deleteLine(final int i) { deleteLine(getDoc(), i); } /** * Deletes a line from the document * * @param i */ public static void deleteLine(final IDocument doc, final int i) { try { final IRegion lineInformation = doc.getLineInformation(i); final int offset = lineInformation.getOffset(); int length = -1; if (doc.getNumberOfLines() > i) { final int nextLineOffset = doc.getLineInformation(i + 1).getOffset(); length = nextLineOffset - offset; } else { length = lineInformation.getLength(); } if (length > -1) { doc.replace(offset, length, ""); } } catch (final BadLocationException e) { ErlLogger.error(e); } } public void deleteSpacesAfter(final int offset) { try { final int len = countSpacesAfter(offset); if (len > 0) { doc.replace(offset, len, ""); } } catch (final Exception e) { // ignore } } public int countSpacesAfter(final int offset0) throws BadLocationException { int offset = offset0; if (offset >= doc.getLength()) { return 0; } final int initial = offset; String next = doc.get(offset, 1); // don't delete 'all' that is considered whitespace (as \n and \r) try { while (next.charAt(0) == ' ' || next.charAt(0) == '\t') { offset++; next = doc.get(offset, 1); } } catch (final Exception e) { // ignore } return offset - initial; } /** * Deletes the current selected text * * @throws BadLocationException */ public void deleteSelection() throws BadLocationException { final int offset = textSelection.getOffset(); doc.replace(offset, textSelection.getLength(), ""); } public void addLine(final String contents, final int afterLine) { addLine(getDoc(), getEndLineDelim(), contents, afterLine); } /** * Adds a line to the document. * * @param doc * the document * @param endLineDelim * the delimiter that should be used * @param contents * what should be added (the end line delimiter may be added * before or after those contents (depending on what are the * current contents of the document). * @param afterLine * the contents should be added after the line specified here. */ public static void addLine(final IDocument doc, final String endLineDelim, final String contents0, final int afterLine) { String contents = contents0; try { int offset = -1; if (doc.getNumberOfLines() > afterLine) { offset = doc.getLineInformation(afterLine + 1).getOffset(); } else { offset = doc.getLineInformation(afterLine).getOffset(); } if (doc.getNumberOfLines() - 1 == afterLine) { contents = endLineDelim + contents; } if (!contents.endsWith(endLineDelim)) { contents += endLineDelim; } if (offset >= 0) { doc.replace(offset, 0, contents); } } catch (final BadLocationException e) { ErlLogger.error(e); } } /** * @return the line where the cursor is (from the cursor position to the end * of the line). * @throws BadLocationException */ public String getLineContentsFromCursor() throws BadLocationException { final int lineOfOffset = getDoc().getLineOfOffset(getAbsoluteCursorOffset()); final IRegion lineInformation = getDoc().getLineInformation(lineOfOffset); final String lineToCursor = getDoc().get(getAbsoluteCursorOffset(), lineInformation.getOffset() + lineInformation.getLength() - getAbsoluteCursorOffset()); return lineToCursor; } /** * @param ps * @return the line where the cursor is (from the beggining of the line to * the cursor position). * @throws BadLocationException */ public String getLineContentsToCursor() throws BadLocationException { final int lineOfOffset = getDoc().getLineOfOffset(getAbsoluteCursorOffset()); final IRegion lineInformation = getDoc().getLineInformation(lineOfOffset); final String lineToCursor = getDoc().get(lineInformation.getOffset(), getAbsoluteCursorOffset() - lineInformation.getOffset()); return lineToCursor; } /** * Readjust the selection so that the whole document is selected. * * @param onlyIfNothingSelected * : If false, check if we already have a selection. If we have a * selection, it is not changed, however, if it is true, it * always selects everything. */ public void selectAll(final boolean forceNewSelection) { if (!forceNewSelection) { if (getSelLength() > 0) { return; } } textSelection = new TextSelection(doc, 0, doc.getLength()); } /** * @return Returns the startLineIndex. */ public int getStartLineIndex() { return getTextSelection().getStartLine(); } /** * @return Returns the endLineIndex. */ public int getEndLineIndex() { return getTextSelection().getEndLine(); } /** * @return Returns the doc. */ public IDocument getDoc() { return doc; } /** * @return Returns the selLength. */ public int getSelLength() { return getTextSelection().getLength(); } /** * @return Returns the selection. */ public String getCursorLineContents() { try { final int start = getStartLine().getOffset(); final int end = getEndLine().getOffset() + getEndLine().getLength(); return doc.get(start, end - start); } catch (final BadLocationException e) { ErlLogger.info(e); } return ""; } /** * @return the delimiter that should be used for the passed document */ public static String getDelimiter(final IDocument doc) { return TextUtilities.getDefaultLineDelimiter(doc); } /** * @return Returns the endLineDelim. */ public String getEndLineDelim() { return getDelimiter(getDoc()); } /** * @return Returns the startLine. */ public IRegion getStartLine() { try { return getDoc().getLineInformation(getStartLineIndex()); } catch (final BadLocationException e) { ErlLogger.info(e); } return null; } /** * @return Returns the endLine. */ public IRegion getEndLine() { try { final int endLineIndex = getEndLineIndex(); if (endLineIndex == -1) { return null; } return getDoc().getLineInformation(endLineIndex); } catch (final BadLocationException e) { ErlLogger.info(e); } return null; } /** * @return Returns the cursorLine. */ public int getCursorLine() { return getTextSelection().getEndLine(); } /** * @return Returns the absoluteCursorOffset. */ public int getAbsoluteCursorOffset() { return getTextSelection().getOffset(); } /** * @return Returns the textSelection. */ public ITextSelection getTextSelection() { return textSelection; } /** * @return the Selected text */ public String getSelectedText() { final ITextSelection txtSel = getTextSelection(); final int start = txtSel.getOffset(); final int len = txtSel.getLength(); try { return doc.get(start, len); } catch (final BadLocationException e) { throw new RuntimeException(e); } } /** * @return * @throws BadLocationException */ public char getCharAfterCurrentOffset() throws BadLocationException { return getDoc().getChar(getAbsoluteCursorOffset() + 1); } /** * @return * @throws BadLocationException */ public char getCharAtCurrentOffset() throws BadLocationException { return getDoc().getChar(getAbsoluteCursorOffset()); } /** * @return the offset mapping to the end of the line passed as parameter. * @throws BadLocationException */ public int getEndLineOffset(final int line) throws BadLocationException { final IRegion lineInformation = doc.getLineInformation(line); return lineInformation.getOffset() + lineInformation.getLength(); } /** * @return the offset mapping to the end of the current 'end' line. */ public int getEndLineOffset() { final IRegion endLine = getEndLine(); return endLine.getOffset() + endLine.getLength(); } /** * @return the offset mapping to the start of the current line. */ public int getStartLineOffset() { final IRegion startLine = getStartLine(); return startLine.getOffset(); } /** * @return the current token and its initial offset for this token * @throws BadLocationException */ @SuppressWarnings("boxing") public Pair<String, Integer> getCurrToken() throws BadLocationException { final Pair<String, Integer> tup = extractActivationToken(doc, getAbsoluteCursorOffset(), false); final String prefix = tup.getKey(); // ok, now, get the rest of the token, as we already have its prefix final int start = tup.getValue() - prefix.length(); int end = start; while (doc.getLength() - 1 >= end) { final char ch = doc.getChar(end); if (Character.isJavaIdentifierPart(ch)) { end++; } else { break; } } final String post = doc.get(tup.getValue(), end - tup.getValue()); return new Pair<>(prefix + post, start); } /** * This function replaces all the contents in the current line before the * cursor for the contents passed as parameter */ public void replaceLineContentsToSelection(final String newContents) throws BadLocationException { final int lineOfOffset = getDoc().getLineOfOffset(getAbsoluteCursorOffset()); final IRegion lineInformation = getDoc().getLineInformation(lineOfOffset); getDoc().replace(lineInformation.getOffset(), getAbsoluteCursorOffset() - lineInformation.getOffset(), newContents); } /** * This function goes backward in the document searching for an 'if' and * returns the line that has it. * * May return null if it was not found. */ public String getPreviousLineThatStartsWithToken(final String[] tokens) { final DocIterator iterator = new DocIterator(false, this); while (iterator.hasNext()) { final String line = iterator.next(); final String trimmed = line.trim(); for (final String prefix : tokens) { if (trimmed.startsWith(prefix)) { return line; } } } return null; } /** * @param theDoc * @param documentOffset * @return * @throws BadLocationException */ public static int eatFuncCall(final IDocument theDoc, final int documentOffset0) throws BadLocationException { int documentOffset = documentOffset0; final String c = theDoc.get(documentOffset, 1); if (!")".equals(c)) { throw new AssertionError("Expecting ) to eat callable. Received: " + c); } while (documentOffset > 0 && !theDoc.get(documentOffset, 1).equals("(")) { documentOffset -= 1; } return documentOffset; } /** * Checks if the activationToken ends with some char from cs. */ public static boolean endsWithSomeChar(final char[] cs, final String activationToken) { for (int i = 0; i < cs.length; i++) { if (activationToken.endsWith(cs[i] + "")) { return true; } } return false; } public static List<Integer> getLineStartOffsets(final String replacementString) { final ArrayList<Integer> ret = new ArrayList<>(); ret.add(0);// there is always a starting one at 0 // we may have line breaks with \r\n, or only \n or \r for (int i = 0; i < replacementString.length(); i++) { char c = replacementString.charAt(i); if (c == '\r') { i++; int foundAt = i; if (i < replacementString.length()) { c = replacementString.charAt(i); if (c == '\n') { // i++; foundAt = i + 1; } } ret.add(foundAt); } else if (c == '\n') { ret.add(i + 1); } } return ret; } public static List<Integer> getLineBreakOffsets(final String replacementString) { final ArrayList<Integer> ret = new ArrayList<>(); int ignoreNextNAt = -1; // we may have line breaks with \r\n, or only \n or \r for (int i = 0; i < replacementString.length(); i++) { final char c = replacementString.charAt(i); if (c == '\r') { ret.add(i); ignoreNextNAt = i + 1; } else if (c == '\n') { if (ignoreNextNAt != i) { ret.add(i); } } } return ret; } /** * @return the number of line breaks in the passed string. */ public static int countLineBreaks(final String replacementString) { int lineBreaks = 0; int ignoreNextNAt = -1; // we may have line breaks with \r\n, or only \n or \r for (int i = 0; i < replacementString.length(); i++) { final char c = replacementString.charAt(i); if (c == '\r') { lineBreaks++; ignoreNextNAt = i + 1; } else if (c == '\n') { if (ignoreNextNAt != i) { lineBreaks++; } } } return lineBreaks; } /** * This function gets the activation token from the document given the * current cursor position. * * @param document * this is the document we want info on * @param offset * this is the cursor position * @param getFullQualifier * if true we get the full qualifier (even if it passes the * current cursor location) * @return a tuple with the activation token and the cursor offset (may * change if we need to get the full qualifier, otherwise, it is the * same offset passed as a parameter). */ public static Pair<String, Integer> extractActivationToken(final IDocument document, final int offset0, final boolean getFullQualifier) { int offset = offset0; try { if (getFullQualifier) { // if we have to get the full qualifier, we'll have to walk the // offset (cursor) forward while (offset < document.getLength()) { final char ch = document.getChar(offset); if (Character.isJavaIdentifierPart(ch)) { offset++; } else { break; } } } int i = offset; if (i > document.getLength()) { return new Pair<>("", document.getLength()); //$NON-NLS-1$ } while (i > 0) { final char ch = document.getChar(i - 1); if (!Character.isJavaIdentifierPart(ch)) { break; } i--; } return new Pair<>(document.get(i, offset - i), offset); } catch (final BadLocationException e) { return new Pair<>("", offset); //$NON-NLS-1$ } } /** * @param c * @param string */ public static boolean containsOnly(final char c, final String string) { for (int i = 0; i < string.length(); i++) { if (string.charAt(i) != c) { return false; } } return true; } /** * @param string * the string we care about * @return true if the string passed is only composed of whitespaces (or * characters that are regarded as whitespaces by * Character.isWhitespace) */ public static boolean containsOnlyWhitespaces(final String string) { for (int i = 0; i < string.length(); i++) { if (!Character.isWhitespace(string.charAt(i))) { return false; } } return true; } /** * @param selection * the text from where we want to get the indentation * @return a string representing the whitespaces and tabs befor the first * char in the passed line. */ public static String getIndentationFromLine(final String selection) { final int firstCharPosition = getFirstCharPosition(selection); return selection.substring(0, firstCharPosition); } public String getIndentationFromLine() { return getIndentationFromLine(getCursorLineContents()); } /** * @param src * @return */ public static int getFirstCharPosition(final String src) { int i = 0; boolean breaked = false; while (i < src.length()) { if (!Character.isWhitespace(src.charAt(i)) && src.charAt(i) != '\t') { i++; breaked = true; break; } i++; } if (!breaked) { i++; } return i - 1; } /** * @param doc * @param region * @return * @throws BadLocationException */ public static int getFirstCharRelativePosition(final IDocument doc, final IRegion region) throws BadLocationException { final int offset = region.getOffset(); final String src = doc.get(offset, region.getLength()); return getFirstCharPosition(src); } /** * @param doc * @param cursorOffset * @return * @throws BadLocationException */ public static int getFirstCharRelativeLinePosition(final IDocument doc, final int line) throws BadLocationException { IRegion region; region = doc.getLineInformation(line); return getFirstCharRelativePosition(doc, region); } /** * @param doc * @param cursorOffset * @return * @throws BadLocationException */ public static int getFirstCharRelativePosition(final IDocument doc, final int cursorOffset) throws BadLocationException { IRegion region; region = doc.getLineInformationOfOffset(cursorOffset); return getFirstCharRelativePosition(doc, region); } /** * Returns the position of the first non whitespace char in the current * line. * * @param doc * @param cursorOffset * @return position of the first character of the line (returned as an * absolute offset) * @throws BadLocationException */ public static int getFirstCharPosition(final IDocument doc, final int cursorOffset) throws BadLocationException { IRegion region; region = doc.getLineInformationOfOffset(cursorOffset); final int offset = region.getOffset(); return offset + getFirstCharRelativePosition(doc, cursorOffset); } /** * Class to help iterating through the document */ public static class DocIterator implements Iterator<String> { private int startingLine; private final boolean forward; private boolean isFirst = true; private final int numberOfLines; private int lastReturnedLine = -1; private final ErlideSelection ps; public DocIterator(final boolean forward, final ErlideSelection ps) { this(forward, ps, ps.getCursorLine(), true); } public DocIterator(final boolean forward, final ErlideSelection ps, final int startingLine, final boolean considerFirst) { this.startingLine = startingLine; this.forward = forward; numberOfLines = ps.getDoc().getNumberOfLines(); this.ps = ps; if (!considerFirst) { isFirst = false; } } public int getCurrentLine() { return startingLine; } @Override public boolean hasNext() { if (forward) { return startingLine < numberOfLines; } return startingLine >= 0; } /** * Note that the first thing it returns is the lineContents to cursor * (and only after that does it return from the full line -- if it is * iterating backwards). */ @Override public String next() { try { String line; if (forward) { line = ps.getLine(startingLine); lastReturnedLine = startingLine; startingLine++; } else { if (isFirst) { line = ps.getLineContentsToCursor(); isFirst = false; } else { line = ps.getLine(startingLine); } lastReturnedLine = startingLine; startingLine--; } return line; } catch (final Exception e) { throw new RuntimeException(e); } } @Override public void remove() { throw new RuntimeException("Remove not implemented."); } public int getLastReturnedLine() { return lastReturnedLine; } } /** * @return if the offset is inside the region */ public static boolean isInside(final int offset, final IRegion region) { if (offset >= region.getOffset() && offset <= region.getOffset() + region.getLength()) { return true; } return false; } /** * @return if the col is inside the initial col/len */ public static boolean isInside(final int col, final int initialCol, final int len) { if (col >= initialCol && col <= initialCol + len) { return true; } return false; } /** * @return if the region passed is composed of a single line */ public static boolean endsInSameLine(final IDocument document, final IRegion region) { try { final int startLine = document.getLineOfOffset(region.getOffset()); final int end = region.getOffset() + region.getLength(); final int endLine = document.getLineOfOffset(end); return startLine == endLine; } catch (final BadLocationException e) { return false; } } /** * @param offset * the offset we want info on * @return a tuple with the line, col of the passed offset in the document */ public Pair<Integer, Integer> getLineAndCol(final int offset) { try { final IRegion region = doc.getLineInformationOfOffset(offset); final int line = doc.getLineOfOffset(offset); final int col = offset - region.getOffset(); return new Pair<>(line, col); } catch (final BadLocationException e) { throw new RuntimeException(e); } } /** * @return the contents from the document starting at the cursor line until * a colon is reached. */ public String getToColon() { final StringBuffer buffer = new StringBuffer(); for (int i = getLineOffset(); i < doc.getLength(); i++) { try { final char c = doc.getChar(i); buffer.append(c); if (c == ':') { return buffer.toString(); } } catch (final BadLocationException e) { throw new RuntimeException(e); } } return ""; // unable to find a colon } public static boolean isIdentifier(final String str) { return IdentifierPattern.matcher(str).matches(); } private static final Pattern IdentifierPattern = Pattern.compile("\\w*"); public static boolean isCommentLine(final String line) { for (int j = 0; j < line.length(); j++) { final char c = line.charAt(j); if (c != ' ') { if (c == '%') { // ok, it starts with % (so, it is a comment) return true; } } } return false; } public IRegion getRegion() { return new Region(textSelection.getOffset(), textSelection.getLength()); } }