package org.basex.gui.layout; import java.util.Arrays; import org.basex.util.TokenBuilder; import static org.basex.util.Token.*; /** * This class allows the iteration on tokens. * * @author BaseX Team 2005-12, BSD License * @author Christian Gruen */ public final class BaseXTextTokens { /** Tab width. */ static final int TAB = 2; /** Text array to be written. */ byte[] text = EMPTY; /** Text length. */ private int size; /** Current start position. */ private int ps; /** Current end position. */ private int pe; /** Current cursor position. */ private int pc; /** Start of a text mark. */ private int ms = -1; /** End of a text mark. */ private int me = -1; /** Start of an error mark. */ private int es = -1; /** * Constructor. * @param t text */ BaseXTextTokens(final byte[] t) { this(t, t.length); } /** * Constructor. * @param t text * @param s buffer size */ BaseXTextTokens(final byte[] t, final int s) { text = t; size = s; } /** * Initializes the iterator. */ void init() { ps = 0; pe = 0; } /** * Checks if the text contains more words. * @return result of check */ boolean moreWords() { // quit if text has ended if(pe >= size) return false; ps = pe; // find next token boundary int ch = cp(text, ps); pe += cl(text, ps); if(!ftChar(ch)) return true; while(pe < size) { ch = cp(text, pe); if(!ftChar(ch)) break; pe += cl(text, pe); } return true; } /** * Returns the token as word. * @return word */ public String nextWord() { return string(text, ps, pe - ps); } /** * Returns the length of the current word. * @return length */ int length() { return pe - ps; } /** * Moves one character forward. * @param mark mark flag * @return character */ int next(final boolean mark) { return noMark(mark, true) ? curr() : next(); } /** * Moves one token forward. * @param mark mark flag */ void nextToken(final boolean mark) { int ch = next(mark); if(ch == '\n') return; if(Character.isLetterOrDigit(ch)) { while(Character.isLetterOrDigit(ch)) ch = next(); while(ch != '\n' && Character.isWhitespace(ch)) ch = next(); } else if(Character.isWhitespace(ch)) { while(ch != '\n' && Character.isWhitespace(ch)) ch = next(); } else { while(ch != '\n' && !Character.isLetterOrDigit(ch) && !Character.isWhitespace(ch)) ch = next(); while(ch != '\n' && Character.isWhitespace(ch)) ch = next(); } if(ps != size) prev(); } /** * Moves one token back. * @param mark mark flag */ void prevToken(final boolean mark) { int ch = prev(mark); if(ch == '\n') return; if(Character.isLetterOrDigit(ch)) { while(Character.isLetterOrDigit(ch)) ch = prev(); } else if(Character.isWhitespace(ch)) { while(ch != '\n' && Character.isWhitespace(ch)) ch = prev(); while(Character.isLetterOrDigit(ch)) ch = prev(); } else { while(ch != '\n' && !Character.isLetterOrDigit(ch) && !Character.isWhitespace(ch)) ch = prev(); } if(ps != 0) next(); } /** * Checks if the character position equals the word end. * @return result of check */ boolean more() { return ps < pe; } /** * Returns the current character. * @return current character */ public int curr() { return ps >= size ? '\n' : cp(text, ps); } /** * Moves one character forward. * @return character */ int next() { final int c = curr(); if(ps < size) ps += cl(text, ps); return c; } /** * Sets the iterator position. * @param p iterator position */ void pos(final int p) { ps = p; } /** * Returns the iterator position. * @return iterator position */ int pos() { return ps; } /** * Returns the byte array, chopping the unused bytes. * @return character array */ byte[] toArray() { return text.length == size ? text : Arrays.copyOf(text, size); } // POSITION ================================================================= /** * Moves to the beginning of the line. * @param mark mark flag * @return number of moved characters */ int bol(final boolean mark) { int c = 0; if(ps == 0) return 0; do c += curr() == '\t' ? TAB : 1; while(prev(mark) != '\n'); if(ps != 0 || curr() == '\n') next(mark); return c; } /** * Moves one character back and returns the found character. * @param mark mark flag * @return character */ int prev(final boolean mark) { return noMark(mark, false) ? curr() : prev(); } /** * Moves one character back and returns the found character. * @return character */ int prev() { if(ps == 0) return '\n'; final int p = ps; ps = Math.max(0, ps - 5); while(ps < p && ps + cl(text, ps) < p) ++ps; return curr(); } /** * Moves to the specified position of to the of the line. * @param p position to move to * @param mark mark flag */ void forward(final int p, final boolean mark) { int nc = 0; while(curr() != '\n') { if((nc += curr() == '\t' ? TAB : 1) >= p) return; next(mark); } } /** * Adds a string at the current position. * @param str string */ void add(final String str) { final TokenBuilder tb = new TokenBuilder(); tb.add(text, 0, ps); int cc = 0; final int cl = str.length(); for(int c = 0; c < cl; ++c) { // ignore invalid characters int ch = str.charAt(c); if(ch == '\r') continue; if(ch < ' ' && !ws(ch)) ch = '\n'; tb.add(ch); ++cc; } tb.add(text, ps, size); text = tb.finish(); size = tb.size(); for(int c = 0; c < cc; ++c) next(); } /** * Indents lines. * @param s start position * @param e end position * @param sh shift flag */ void indent(final int s, final int e, final boolean sh) { // extend selection to match whole lines pos(s); bol(true); startMark(); pos(e); forward(Integer.MAX_VALUE, true); next(true); endMark(); // decide if to use tab or spaces boolean tab = false; for(int p = 0; p < size; ++p) tab |= text[p] == '\t'; byte[] add = { '\t' }; if(!tab) { add = new byte[TAB]; for(int a = 0; a < TAB; a++) add[a] = ' '; } // build new text final TokenBuilder tb = new TokenBuilder(); tb.add(text, 0, ms); for(int p = ms; p < ps; p += cl(text, p)) { if(p == 0 || text[p - 1] == '\n') { if(sh) { // remove indentation if(text[p] == '\t') { me--; continue; } if(text[p] == ' ') { me--; for(int i = 1; i < TAB && p + i < size && text[p + i] == ' '; i++) { me--; p++; } continue; } } else { // add new indentation tb.add(add); me += add.length; } } tb.add(cp(text, p)); } tb.add(text, ps, size); ps = me; text = tb.finish(); size = tb.size(); } /** * (Un)comments highlighted text or line. * @param syntax syntax highlighter */ void comment(final BaseXSyntax syntax) { final byte[] start = syntax.commentOpen(); final byte[] end = syntax.commentEnd(); boolean add = true; int min = ps; int max = ps; if(marked()) { min = ps < ms ? ps : ms; max = ps > ms ? ps : ms; // marked final int mn = Math.max(min + start.length, max - end.length); if(indexOf(text, start, min) == min && indexOf(text, end, mn) == mn) { final TokenBuilder tb = new TokenBuilder(); tb.add(text, 0, min); tb.add(text, min + start.length, max - end.length); tb.add(text, max, size); text = tb.finish(); size = tb.size(); ms = min; me = max - start.length - end.length; ps = me; add = false; } } else { while(min > 0 && text[min - 1] != '\n') --min; while(max < size() && text[max] != '\n') ++max; } if(add) { pos(max); add(string(end)); pos(min); add(string(start)); ms = min; me = max + start.length + end.length; ps = me; } } /** * Deletes the current character. * Assumes that the current position allows a deletion. */ void delete() { if(size == 0) return; final TokenBuilder tb = new TokenBuilder(); final int s = marked() ? Math.min(ms, me) : ps; final int e = marked() ? Math.max(ms, me) : ps + cl(text, ps); tb.add(text, 0, s); if(e < size) tb.add(text, e, size); text = tb.finish(); size = tb.size(); ps = s; noMark(); } // TEXT MARKING ============================================================= /** * Jumps to the maximum/minimum position and resets the selection. * @param mark marking flag * @param max maximum/minimum flag * @return true if mark was reset */ private boolean noMark(final boolean mark, final boolean max) { final boolean rs = !mark && marked(); if(rs) { ps = max ^ ms < me ? ms : me; noMark(); } return rs; } /** * Resets the selection. */ void noMark() { ms = -1; me = -1; } /** * Sets the start of a text mark. */ void startMark() { ms = ps; me = ps; } /** * Sets the end of a text mark. */ void endMark() { me = ps; } /** * Returns the start of the text mark. The value is {@code -1} if no * text is selected. * @return start mark */ int start() { return ms; } /** * Tests if some text is marked. * @return result of check */ boolean marked() { return ms != -1; } /** * Checks the validity of the mark. */ void checkMark() { if(ms == me) noMark(); } /** * Tests if the current position is marked. * @return result of check */ boolean markStart() { return marked() && (inMark() || (ms < me ? ms >= ps && ms < pe : me >= ps && me < pe)); } /** * Tests if the current position is marked. * @return result of check */ boolean inMark() { return ms < me ? ps >= ms && ps < me : ps >= me && ps < ms; } /** * Returns the marked substring. * @return substring */ String copy() { if(!marked()) return ""; final TokenBuilder tb = new TokenBuilder(); final int e = ms < me ? me : ms; for(int s = ms < me ? ms : me; s < e; s += cl(text, s)) { final int t = cp(text, s); if(t < 0 || t >= ' ' || t == 0x0A || t == 0x09) tb.add(t); } return tb.toString(); } // ERROR MARK =============================================================== /** * Tests if the current token is erroneous. * @return result of check */ boolean error() { return es >= ps && es <= pe; } /** * Tests if the cursor moves over the current token. * @param s start */ void error(final int s) { es = s; } // CURSOR =================================================================== /** * Checks if the cursor moves over the current token. * @return result of check */ boolean edited() { return pc >= ps && pc <= pe; } /** * Sets the caret position to the specified counter. * @param c caret position */ void setCaret(final int c) { pc = c; } /** * Sets the caret position to the current counter. */ void setCaret() { pc = ps; } /** * Returns the cursor position. * @return cursor position */ int cursor() { return pc; } /** * Returns the text size. * @return text size */ int size() { return size; } @Override public String toString() { return copy(); } }