/* VStream.java * * Copyright (c) 2000, Ted Nelson and Tuomas Lukka * * You may use and distribute under the terms of either the GNU Lesser * General Public License, either version 2 of the license or, * at your choice, any later version. Alternatively, you may use and * distribute under the terms of the XPL. * * See the LICENSE.lgpl and LICENSE.xpl files for the specific terms of * the licenses. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the README * file for more details. * */ /* * Written by Tuomas Lukka */ package org.gzigzag; import java.util.*; import java.awt.*; /** Utility functions for vstreams. * NOTE! USE THE VirtualCursor OBJECTS ONLY SHORT-TERM, put stuff * in the structure * for longer term. * ANY call to VStream.* routines invalidates all VirtualCursor objects pointing * to that vstream. *

* TODO: d.markup, active references to parts of another vstream * (via including complete vstream objects!) *

* The vstream format is simple: the headcell on d.2 is a dummy cell * that is never moved (so that it remains easy to refer to the vstream). * Note that cells with the span length of 0 (i.e. end less than start) * are explicitly ALLOWED. *

* Important policy decision pending: what to do about non-span cells * in vstreams? *

* In addition to pointers, we'll probably need spans (of vstream) to complement * them, for formatting etc. * @deprecated This is a bit old now - see e.g. Nile and Stream.java. */ public class VStream { public static final String rcsid = "$Id: VStream.java,v 1.28 2000/11/24 16:24:21 tjl Exp $"; public static final boolean dbg = false; static final void p(String s) { if(dbg) System.out.println(s); } static String dim = "d.2"; static public class Rend { public Span[] spans; public String[] strs; public ZZCell[] cells; public int[][] marks; public ZZCell[][] markcells; } static public final void assertOffs(ZZCursor c) { if(c.getOffs() == c.NO_OFFSET) throw new ZZError("Need cursor offset"); } static public ZZCursorVirtual add(ZZCursor vc, int o) { assertOffs(vc); ZZCell c = vc.get(); o += vc.getOffs(); // XXX Do we need to enforce spans? while(o < 0) { c = c.s(dim, -1); if(c == null || c.getSpan() == null) return null; o += (int)c.getSpan().length(); } while(o > c.getSpan().length()) { o -= (int)c.getSpan().length(); c = c.s(dim, 1); if(c == null || c.getSpan() == null) return null; } return new ZZCursorVirtual(c, o); } static public String error(ZZCell c) { ZZCell s = c.h(dim, -1); if(s.getSpan() != null) return "First cell has span"; while((s=s.s(dim, 1))!=null) { if(s.getSpan() == null) return "No span in cell!"; } return null; } static public void check(ZZCell c) { if(!dbg) return; String e = error(c); System.out.println("VSTREAM DUMP"); for(ZZCell s = c.h(dim, -1); s != null; s = s.s(dim, 1)) { System.out.println("'"+s.getText()+"' from span: "+s.getSpan()); } if(e == null) return; throw new ZZError("VStream error in stream! '"+e+"' dumped"); } // The first cell is the second of the rank. static public ZZCursor start(ZZCell c) { c = c.h(dim, -1).s(dim, 1); if(c==null) return null; return new ZZCursorVirtual(c, 0); } static public ZZCursor end(ZZCell c) { c = c.h(dim, 1); return new ZZCursorVirtual(c, c.getText().length()); } static public void create(ZZCell c, String dim, int dir, Span s) { ZZCell n = c.N(dim, dir).N("d.2", 1); if(s != null) n.setSpan(s); } /** Find the first instance of the given character * that occurs at position p or after. */ static public ZZCursor findChar(char c, ZZCursor p) { assertOffs(p); String s = p.get().getText(); int io = s.indexOf(c, p.getOffs()); if(io != -1) return new ZZCursorVirtual(p.get(), io); ZZCell cur = p.get(); while((cur = cur.s(dim, 1))!=null) { io = cur.getText().indexOf(c); if(io != -1) return new ZZCursorVirtual(cur, io); } return null; } /** Return the string between two positions, inclusive. */ static public String getStr(ZZCursor p1, ZZCursor p2) { p("getStr: "+p1+" "+p2); assertOffs(p1); assertOffs(p2); StringBuffer s = new StringBuffer(); if(p1.get() == p2.get()) { return p1.get().getText().substring(p1.getOffs(), p2.getOffs()+1); } s.append(p1.get().getText().substring(p1.getOffs())); ZZCell c = p1.get(); while((c=c.s(dim,1)) != null && c != p2.get()) { s.append(c.getText()); } if(c!=null) { String str = c.getText(); // Have to check whether one past end or end if(str.length() > p2.getOffs()) s.append(str.substring(0, p2.getOffs()+1)); else s.append(str); } return s.toString(); } // XXX Join the next two methods static public Span[] getSpans(ZZCursor p1, ZZCursor p2) { Vector sp = new Vector(); p("getspans: "+p1+" "+p2+" "+p1.get()+" "+p2.get()); p("getspans1: "+p1.get().getText()); p("getspans2: "+p1.get().getSpan()); p("getspans3: "+p1.get().getSpan().subSpan(p1.getOffs())); if(p1.get() == p2.get()) { sp.addElement( p1.get().getSpan().subSpan(p1.getOffs(), p2.getOffs()+1) ); } else { sp.addElement( p1.get().getSpan().subSpan(p1.getOffs()) ); ZZCell c = p1.get(); while((c=c.s(dim,1)) != null && c != p2.get()) { sp.addElement(c.getSpan()); } if(c!=null) sp.addElement(c.getSpan().subSpan(0, p2.getOffs()+1)); } Span[] ret = new Span[sp.size()]; for(int i=0; i= o1) && (o2<0 || i <= o2)) { if(res==null) res = new Vector(); res.addElement(new Integer(i-o1)); res.addElement(c); } } catch(NumberFormatException e) { // Nothin' } } if(res!=null) { int[] r = new int[res.size()/2]; ZZCell[] rc = new ZZCell[res.size()]; for(int i=0 ;i= offs0) && (offs1 < -1 || of <= offs1)) { rnk[i].setText(""+(of-offs0+noffs)); r.insert("d.cursor", 1, rnk[i]); } } catch(NumberFormatException e) { // Nothin' } } } /** Split a VStream at position, return the cell just BEFORE the * split. XXX MAYBE ALL VSTREAMS SHOULD HAVE EMPTY CELL AT BEGINNING AND * END. */ static private ZZCell splitAt(ZZCursor p) { // If offs == 0, need to do nothing. if(p.getOffs() == 0) return p.get().s(dim, -1); Span cur = p.get().getSpan(); if(p.getOffs() == cur.length()) return p.get(); // In the middle -> split and reposition cursors. ZZCell n1 = p.get().N(dim, 1); n1.setSpan(cur.subSpan(p.getOffs())); p.get().setSpan(cur.subSpan(0, p.getOffs())); ZZCell[] rnk = p.get().readRank("d.cursor", 1, false); for(int i=0; i= p.getOffs()) { int nof = of-p.getOffs(); rnk[i].setText(""+nof); n1.insert("d.cursor", 1, rnk[i]); } } catch(NumberFormatException e) { // Nothin' } } return p.get(); } /** Insert a cut mark at the given position. */ static public void cut(ZZCursor p) { ZZCell prev = splitAt(p); ZZCell n = prev.s(dim, 1); n.disconnect("d.cut", 1); n.connect("d.cut", 1, n); } /** Clear the cuts in the given vstream. */ static public void clearcuts(ZZCursor p) { if ( p == null ) return ; for(ZZCell c = p.get().h(dim, -1); c!=null; c = c.s(dim, 1)) if(c.s("d.cut", 1) != null) c.disconnect("d.cut", 1); } static ZZCell findPrevCut(ZZCursor p) { ZZCell prev; for(prev=p.get(); prev != null && prev.s("d.cut", 1) == null; prev = prev.s(dim, -1)); return prev; } static ZZCell findNextCut(ZZCursor p) { ZZCell next; for(next=p.get().s(dim, 1); next != null && next.s("d.cut", 1) == null; next = next.s(dim, 1)); return next; } /** Create a new vstream between the given cut marks. * XXX This is difficult since Pos doesn't have the forwards-backwards * info - Swing positions do. This should probably be added... * Imagine cutting apart at single character edges... */ static public ZZCell separate(ZZCursor p) { ZZCell prev = findPrevCut(p); ZZCell next = findNextCut(p); ZZCell tmp; // based on their nullness, find the section to cut. if(prev == null && next == null) return null; // nothing. if(prev == null) { next.disconnect("d.cut", 1); next.disconnect(dim, -1); tmp = p.get().h(dim, -1); tmp.disconnect(dim, 1); tmp.connect(dim, 1, next); return p.get().h(dim, -1).N(dim, -1); } else { if(next != null){ //prev.s(dim, -1).connect(dim, 1, next); next.disconnect(dim, -1); tmp = prev.s(dim, -1); tmp.disconnect(dim, 1); tmp.connect(dim, 1, next); } prev.disconnect(dim, -1); prev.disconnect("d.cut", 1); return prev.N(dim, -1); } // return null; } /** Copy the part of the vstream between the nearest cut marks * into a new vstream. */ static public ZZCell separateCopy(ZZCursor p) { ZZCell nhead = p.get().N("d.tmp",1); nhead.excise("d.tmp"); ZZCell prev = findPrevCut(p); ZZCell next = findNextCut(p); if(prev == null) prev = p.get().h(dim, -1).s(dim, 1); ZZCell n = nhead; for(ZZCell c = prev; c != null && c != next; c=c.s(dim, 1)) { n = n.N(dim, 1); n.setSpan(c.getSpan()); } return nhead; } /** Insert the given span. * Cursors at the insertion point will be moved to AFTER the * insertion. XXX Here's something where more flexibility * is needed: does a cursor point forwards or backwards? */ static public void insert(ZZCursor p, Span s) { ZZCell prev = splitAt(p); Span cur = prev.getSpan(); if(prev.getSpan() != null && prev.getSpan().isAppendable(s)) { int len = (int)prev.getSpan().length(); moveCurs(prev, len, len, prev, (int)(len+s.length())); prev.setSpan(prev.getSpan().append(s)); return; } ZZCell n = prev.N(dim, 1); n.setSpan(s); moveCurs(p.get(), p.getOffs(), p.getOffs(), n, 1); } /** Assimilate a whole vstream in insertion. * Also places cuts at the beginning and the end of the result... * XXX should it just return the POSes? * XXX Delete the empty cells at ends... */ static public void assimilate(ZZCursor p, ZZCell vstream) { ZZCell prev = splitAt(p); ZZCell next = prev.s(dim, 1); ZZCell firstof = vstream.h(dim, -1).s(dim, 1); ZZCell tail = firstof.h(dim, 1); prev.disconnect(dim, 1); firstof.disconnect(dim, -1); prev.connect(dim, 1, firstof); firstof.connect("d.cut", 1, firstof); if(next != null) { next.disconnect("d.cut", 1); next.connect("d.cut", 1, next); next.connect(dim, -1, tail); } } /** A convenience function: delete one character prior to p0. */ static public void backspace(ZZCursor p0) { // while and not if: there might be null-length // spans in there... while(p0.getOffs() == 0) { ZZCell c = p0.get().s(dim, -1); if(c == null || c.getSpan() == null || c.getSpan().length() == 0) return; p0 = new ZZCursorVirtual(c, (int)c.getSpan().length()); } // Split if needed if(p0.getOffs() != p0.get().getSpan().length()) { ZZCell prev = splitAt(p0); p0.set(prev); } // Now we know p0.offs == p0.get().getSpan().length()... int l = (int)(p0.get().getSpan().length()); if(l==1) { // move cursors and delete cell // XXX AUGH! Must move EITHER forwards or backwards, // depending on which one is there... ZZCell to = p0.get().s(dim, 1); int offs = 0; if(to==null) { to = p0.get().s(dim, -1); offs = (int)to.getSpan().length(); } // Ouch... moveCurs(p0.get(), 0, 0, to, offs); moveCurs(p0.get(), 1, 1, to, offs); p0.get().delete(); return; } p0.get().setSpan(p0.get().getSpan().subSpan(0, l-1)); moveCurs(p0.get(), l, l, p0.get(), l-1); } // XXX Maybe too heavy - always creates new cells. // untested. static public void delete(ZZCursor p0, ZZCursor p1) { ZZCell[] rnk; if(p0.get() == p1.get()) { ZZCell n = p0.get().N(dim, 1); p0.get().setSpan(p0.get().getSpan().subSpan(0, p0.getOffs())); n.setSpan(p0.get().getSpan().subSpan(p1.getOffs()+1)); // Then, redo cursors rnk = p0.get().readRank("d.cursor", 1, false); for(int i=0; i p1.getOffs()) { int nof = of-p1.getOffs()-1; rnk[i].setText(""+nof); n.insert("d.cursor", 1, rnk[i]); } else if(of >= p0.getOffs()) { rnk[i].setText(""+p0.getOffs()); } } catch(NumberFormatException e) { // Nothin' } } } else { // 1. first cell's span & cursors p0.get().setSpan(p0.get().getSpan().subSpan(0, p0.getOffs())); rnk = p0.get().readRank("d.cursor", 1, false); for(int i=0; i= p0.getOffs()) { rnk[i].setText(""+p0.getOffs()); } } catch(NumberFormatException e) { // Nothin' } } // Do the cells until we get to the second cell. ZZCell c = p0.get(); while((c = c.s(dim, 1)) != null && c != p1.get()) { rnk = c.readRank("d.cursor", 1, false); for(int i=0; i p1.getOffs()) { int nof = of-p1.getOffs()-1; rnk[i].setText(""+nof); } else { p0.get().insert("d.cursor", 1, rnk[i]); rnk[i].setText(""+p0.getOffs()); } } catch(NumberFormatException e) { // Nothin' } } } } /** Given a blank cell, set that cell's span and add extra * cells as necessary to copy the span between p0 and p1. */ static public void copyNew(ZZCursor p0, ZZCursor p1, ZZCell blank) { if(p0.get()==p1.get()) { blank.setSpan(p0.get().getSpan().subSpan(p0.getOffs(), p1.getOffs()+1)); } else { // First cell blank.setSpan(p0.get().getSpan().subSpan(p0.getOffs())); // middle cells ZZCell c = p0.get(); ZZCell n = blank; while((c = c.s(dim, 1)) != null && c != p1.get()) { n = n.N("dim", 1); n.setSpan(c.getSpan()); } // Last cell if(c==null) return; n = n.N("dim", 1); n.setSpan(p1.get().getSpan().subSpan(0, p1.getOffs()+1)); } } }