/* * $Id$ * * Copyright (c) 2004-2005 by the TeXlapse Team. * 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 net.sourceforge.texlipse.editor; import java.util.regex.Matcher; import java.util.regex.Pattern; import net.sourceforge.texlipse.TexlipsePlugin; import org.eclipse.jface.text.BadLocationException; import org.eclipse.jface.text.DocumentCommand; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.IRegion; /** * This class handles the line wrapping. * * @author Antti Pirinen * @author Oskar Ojala * @author Boris von Loesch */ public class HardLineWrap { private TexEditorTools tools; private static final Pattern simpleCommandPattern = Pattern.compile("\\\\(\\w+|\\\\)\\s*(\\[.*?\\]\\s*)*(\\{.*?\\}\\s*)*"); public HardLineWrap(){ this.tools = new TexEditorTools(); } /** * Removes all whitespaces from the beginning of the String * @param str The string to wrap * @return trimmed version of the string */ private static String trimBegin (final String str) { int i = 0; while (i < str.length() && (Character.isWhitespace(str.charAt(i)))) i++; return str.substring(i); } /** * Removes all whitespaces and the first "% " from the beginning of the * String. * * Examples: * " hello world" will return "hello world" * " % hello" will return "hello" * " %hello" will return "hello" * " % % hello" will return "% hello" * " %% hello" will return "% hello" * * @param str The string to trim * @return trimmed version of the string */ private static String trimBeginPlusComment (final String str) { int i = 0; while (i < str.length() && (Character.isWhitespace(str.charAt(i)))) i++; if (i < str.length() && str.charAt(i) == '%') i++; if (i < str.length() && str.charAt(i) == ' ') i++; return str.substring(i); } /** * Removes all whitespaces from the end of the String * @param str The string to wrap * @return trimmed version of the string */ private static String trimEnd (final String str) { int i = str.length() - 1; //while (i >= 0 && (str.charAt(i) == ' ' || str.charAt(i) == '\t')) while (i >= 0 && (Character.isWhitespace(str.charAt(i)))) i--; return str.substring(0, i + 1); } /** * This method checks, whether <i>line</i> should stay alone on one line.<br /> * Examples: * <ul> * <li>\begin{env}</li> * <li>% Comments</li> * <li>\command[...]{...}{...}</li> * <li>(empty line)</li> * <li>\\[2em]</li> * </ul> * * @param line * @return */ private static boolean isSingleLine(String line) { if (line.length() == 0) return true; if (line.startsWith("%")) return true; if ((line.startsWith("\\") && line.length() == 2)) return true; // e.g. \\ or \[ if (line.startsWith("\\item")) return true; Matcher m = simpleCommandPattern.matcher(line); if (m.matches()) return true; return false; } /** * Finds the best position in the given String to make a line break * @param line * @param MAX_LENGTH * @return */ private static int getLineBreakPosition(String line, int MAX_LENGTH) { int offset = 0; //Ignore indentation while (offset < line.length() && (line.charAt(offset) == ' ' || line.charAt(offset) == '\t')) { offset++; } int breakOffset = -1; while (offset < line.length()) { if (offset > MAX_LENGTH && breakOffset != -1) break; if (line.charAt(offset) == ' ' || line.charAt(offset) == '\t') { breakOffset = offset; } offset++; } return breakOffset; } /** * New line wrapping strategy. * The actual wrapping method. Based on the <code>IDocument d</code> * and <code>DocumentCommand c</code> the method determines how the * line must be wrapped. * <p> * If there is more than <code>MAX_LENGTH</code> * characters at the line, the method tries to detect the last white * space before <code> MAX_LENGTH</code>. In case there is none, the * method finds the first white space after <code> MAX_LENGTH</code>. * Normally it adds the rest of the currentline to the next line. * Exceptions are empty lines, commandlines, commentlines, and special lines like \\ or \[. * * @param d IDocument * @param c DocumentCommand * @param MAX_LENGTH How many characters are allowed at one line. */ public void doWrapB(IDocument d, DocumentCommand c, int MAX_LENGTH) { try { // Get the line of the command excluding delimiter IRegion commandRegion = d.getLineInformationOfOffset(c.offset); // Ignore texts with line endings if (commandRegion.getLength() + c.text.length() <= MAX_LENGTH || c.text.indexOf("\n") >= 0 || c.text.indexOf("\r") >= 0) return; String line = d.get(commandRegion.getOffset(), commandRegion.getLength()); int lineNr = d.getLineOfOffset(c.offset); final int cursorOnLine = c.offset - commandRegion.getOffset(); //Create the newLine, we rewrite the whole currentline StringBuffer newLineBuf = new StringBuffer(); newLineBuf.append(line.substring(0, cursorOnLine)); newLineBuf.append (c.text); newLineBuf.append(trimEnd(line.substring(cursorOnLine))); //Special case if there are white spaces at the end of the line if (trimEnd(newLineBuf.toString()).length() <= MAX_LENGTH) return; String delim = d.getLineDelimiter(lineNr); boolean isLastLine = false; if (delim == null) { //This is the last line in the document isLastLine = true; if (lineNr > 0) delim = d.getLineDelimiter(lineNr - 1); else { //Last chance String delims[] = d.getLegalLineDelimiters(); delim = delims[0]; } } //String indent = tools.getIndentation(d, c); // TODO check if inside comment String indent = tools.getIndentationWithComment(line); int length = line.length(); String nextline = tools.getStringAt(d, c, false, 1); String nextTrimLine = nextline.trim(); boolean isWithNextline = false; // Figure out whether the next line should be merged with the wrapped text // 1st case: wrapped text ends with . or : if (line.trim().endsWith(".") || line.trim().endsWith(":") || line.trim().endsWith("\\\\")){ newLineBuf.append(delim); // do not merge } else { // 2nd case: merge comment lines if (tools.getIndexOfComment(line) >= 0 // wrapped text contains a comment, && tools.isLineCommentLine(nextTrimLine) // next line is also a comment line, && tools.getIndentation(line).equals(tools.getIndentation(nextline)) // with the same indentation! && !isSingleLine(trimBeginPlusComment(nextTrimLine))) // but not an empty comment line, commented command line, etc. { // merge! newLineBuf.append(' '); newLineBuf.append(trimBeginPlusComment(nextline)); length += nextline.length(); isWithNextline = true; // 3th case: Wrapped text is comment, next line is not (otherwise case 2) } else if (tools.getIndexOfComment(line) >= 0) { newLineBuf.append(delim); // 4rd case: Next line is a comment/command } else if (isSingleLine(nextTrimLine)){ newLineBuf.append(delim); // all other cases } else { // merge: Add the whole next line newLineBuf.append(' '); newLineBuf.append(trimBegin(nextline)); length += nextline.length(); isWithNextline = true; } } // TODO: if line has a comment at the end, this might be wrapped onto a non-comment line // TODO: newLine might need wrapping as well if too long if (!isLastLine) length += delim.length(); //delim.length(); String newLine = newLineBuf.toString(); int breakpos = getLineBreakPosition(newLine, MAX_LENGTH); if (breakpos < 0) return; c.length = length; c.shiftsCaret = false; c.caretOffset = c.offset + c.text.length() + indent.length(); if (breakpos >= cursorOnLine + c.text.length()){ c.caretOffset -= indent.length(); } if (breakpos < cursorOnLine + c.text.length()){ //Line delimiter - one white space c.caretOffset += delim.length() - 1; } c.offset = commandRegion.getOffset(); StringBuffer buf = new StringBuffer(); buf.append(newLine.substring(0, breakpos)); buf.append(delim); buf.append(indent); // Are we wrapping a comment onto the next line without its %? if (tools.getIndexOfComment(newLine.substring(0,breakpos)) >= 0 && tools.getIndexOfComment(indent) == -1) buf.append("% "); buf.append(trimBegin(newLine.substring(breakpos))); //Remove unnecessary characters from buf int i=0; while (i < line.length() && line.charAt(i) == buf.charAt(i)) { i++; } buf.delete(0, i); c.offset += i; c.length -= i; if (isWithNextline) { i=0; while (i < nextline.length() && nextline.charAt(nextline.length()-i-1) == buf.charAt(buf.length()-i-1)) { i++; } buf.delete(buf.length()-i, buf.length()); c.length -= i; } c.text = buf.toString(); } catch(BadLocationException e) { TexlipsePlugin.log("Problem with hard line wrap", e); } } }