/******************************************************************************* * Copyright (c) 2016 Pivotal, Inc. * 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: * Pivotal, Inc. - initial API and implementation *******************************************************************************/ package org.springframework.ide.eclipse.editor.support.util; import java.util.ArrayList; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.eclipse.core.runtime.Assert; import org.eclipse.jface.text.BadLocationException; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.IRegion; import org.eclipse.jface.text.ITypedRegion; import org.eclipse.jface.text.Region; import org.eclipse.jface.text.TypedRegion; /** * A non-sucky alternative to {@link IRegion}. Represents a region of text in a document. * <p> * Caution: assumes the underlying document is not mutated during the lifetime of the * region object (otherwise start/end positions may no longer be valid). * <p> * Implements {@link CharSequence} for convenience (e.g you can use {@link DocumentRegion} as * input to a {@link Pattern} and other standard JRE functions which expect a {@link CharSequence}. * * @author Kris De Volder */ public class DocumentRegion implements CharSequence { final IDocument doc; final int start; final int end; public DocumentRegion(IDocument doc, IRegion r) { this(doc, r.getOffset(), r.getOffset()+r.getLength() ); } /** * Constructs a {@link DocumentRegion} on a given document. Tries its * best to behave sensibly when passed 'strange' coordinates by * adjusting them logically rather than throw an Exception. * <p> * A position before the start of the document is moved to be the start * of the document. * <p> * A position after the end of the document is moved to the end * of the document. * <p> * If 'end' position is before the start position it is moved be * exactly at the start position (this avoids region with * negative length). */ public DocumentRegion(IDocument doc, int start, int end) { this.doc = doc; this.start = limitRange(start, 0, doc.getLength()); this.end = limitRange(end, start, doc.getLength()); } private int limitRange(int offset, int min, int max) { if (offset<min) { return min; } if (offset>max) { return max; } return offset; } @Override public String toString() { return DocumentUtil.textBetween(doc, start, end); } public DocumentRegion trim() { return trimEnd().trimStart(); } public DocumentRegion trimStart() { int howMany = 0; int len = length(); while (howMany<len && Character.isWhitespace(charAt(howMany))) { howMany++; } return subSequence(howMany, len); } public DocumentRegion trimEnd() { int howMany = 0; //how many chars to remove from the end int len = length(); int lastChar = len-1; while (howMany<len && Character.isWhitespace(charAt(lastChar-howMany))) { howMany++; } if (howMany>0) { return subSequence(0, len-howMany); } return this; } /** * Gets character from the region, offset from the start of the region * @return the character from the document (char)0 if the offset is outside the region. */ @Override public char charAt(int offset) { if (offset<0 || offset>=length()) { throw new IndexOutOfBoundsException(""+offset); } try { return doc.getChar(start+offset); } catch (BadLocationException e) { throw new IndexOutOfBoundsException(""+offset); } } @Override public int length() { return end-start; } @Override public DocumentRegion subSequence(int start, int end) { int len = length(); Assert.isLegal(start>=0); Assert.isLegal(end<=len); if (start==0 && end==len) { return this; } return new DocumentRegion(doc, this.start+start, this.start+end); } public boolean isEmpty() { return length()==0; } public DocumentRegion subSequence(int start) { return subSequence(start, length()); } public IRegion asRegion() { return new Region(start, end-start); } public ITypedRegion asTypedRegion(String type) { return new TypedRegion(start, length(), type); } public int indexOf(char ch, int fromIndex) { while (fromIndex < length()) { if (charAt(fromIndex)==ch) { return fromIndex; } fromIndex++; } return -1; } public DocumentRegion[] split(char c) { List<DocumentRegion> pieces = new ArrayList<>(); int start = 0; int end; while ((end=indexOf(c, start))>=0) { pieces.add(subSequence(start, end)); start = end+1; } // Do not forget the last piece! pieces.add(subSequence(start, length())); return pieces.toArray(new DocumentRegion[pieces.size()]); } public DocumentRegion[] split(Pattern delimiter) { List<DocumentRegion> pieces = new ArrayList<>(); int start = 0; Matcher matcher = delimiter.matcher(this); while (matcher.find(start)) { int end = matcher.start(); pieces.add(subSequence(start, end)); start = matcher.end(); } // Do not forget the last piece! pieces.add(subSequence(start, length())); return pieces.toArray(new DocumentRegion[pieces.size()]); } /** * Removes a single occurrence of pat from the start of this region. */ public DocumentRegion trimStart(Pattern pat) { pat = Pattern.compile("^("+pat.pattern()+")"); Matcher matcher = pat.matcher(this); if (matcher.find()) { return subSequence(matcher.end()); } return this; } /** * Removes a single occurrence of pat from the end of this region. */ public DocumentRegion trimEnd(Pattern pat) { pat = Pattern.compile("("+pat.pattern()+")$"); Matcher matcher = pat.matcher(this); if (matcher.find()) { return subSequence(0, matcher.start()); } return this; } /** * Get the region after this one with a given lenght. * <p> * If the document is too short to provide the requested lenght * then the region is truncated to end of the document. */ public DocumentRegion textAfter(int len) { Assert.isLegal(len>=0); return new DocumentRegion(doc, end, end+len); } /** * Get the region before this one with a given lenght. * <p> * If the requested region extends before the start of the document, * then the region is shortened so its start coincides with document start. */ public DocumentRegion textBefore(int len) { Assert.isLegal(len>=0); return new DocumentRegion(doc, start-len, start); } public IDocument getDocument() { return doc; } /** * Get the start of this region in 'absolute' terms (i.e. relative to the document). */ public int getStart() { return start; } /** * Get the end of this region in 'absolute' terms (i.e. relative to the document). */ public int getEnd() { return end; } /** * Convert the given document offset into an offset relative to this region. */ public int toRelative(int offset) { return offset-start; } }