Index: AbstractInterp.java =================================================================== RCS file: /cvs/core/libsrc/org/netbeans/lib/terminalemulator/AbstractInterp.java,v retrieving revision 1.3 diff -u -r1.3 AbstractInterp.java --- AbstractInterp.java 4 Oct 2001 09:59:59 -0000 1.3 +++ AbstractInterp.java 9 Apr 2002 01:03:41 -0000 @@ -24,14 +24,14 @@ public abstract class AbstractInterp implements Interp { protected interface Actor { - public String action(char c); + public String action(AbstractInterp interp, char c); } - protected class State { + protected static class State { // some generic actors Actor act_error = new Actor() { - public String action(char c) { + public String action(AbstractInterp ai, char c) { return "generic error"; // NOI18N } }; @@ -80,9 +80,20 @@ } }; - protected Ops ops; + // Why make these be public and not protected? + // Someone might inherit from us in a package other than org.netbeans + // and while the inherited Interp will see these if they are protected, + // the corresponding InterpType won't. + + /* + */ + public Ops ops; + public State state; // current state + /* + protected Ops ops; protected State state; // current state + */ protected AbstractInterp(Ops ops) { this.ops = ops; @@ -91,5 +102,40 @@ public void reset() { ; } + + /* + * Management of number parsing + */ + + private static final int max_numbers = 5; + private int numberx = 0; + private String number[] = new String[max_numbers]; + + protected void resetNumber() { + for (int x = 0; x < max_numbers; x++) { + number[x] = new String(); + } + numberx = 0; + } + protected void remember_digit(char c) { + number[numberx] += c; + } + protected boolean pushNumber() { + numberx++; + return (numberx < max_numbers); + } + protected boolean noNumber() { + return number[0].equals(""); // NOI18N + } + protected int numberAt(int position) { + if (position > numberx) { + return 1; + } + return Integer.parseInt(number[position]); + } + protected int nNumbers() { + return numberx; + } + } Index: Buffer.java =================================================================== RCS file: /cvs/core/libsrc/org/netbeans/lib/terminalemulator/Buffer.java,v retrieving revision 1.5 diff -u -r1.5 Buffer.java --- Buffer.java 4 Oct 2001 09:59:59 -0000 1.5 +++ Buffer.java 9 Apr 2002 01:03:41 -0000 @@ -49,14 +49,16 @@ private OurVector lines = new OurVector(); // buffer public int nlines; // number of lines in buffer - // How is tis different from lines.length? + // How is this different from lines.length? - private int _cols; - - public int visible_cols; // number of columns visible in view + private int visible_cols; // number of columns visible in view private int extra_cols; // columns needed to support lines longer // than visible_cols. Only grows. + public int visibleCols() { + return visible_cols; + } + public int totalCols() { return visible_cols + extra_cols; } @@ -75,7 +77,7 @@ /* * Keep track of the largest column # to help set the extent of - * the horizaontal scrollbar. + * the horizontal scrollbar. */ public void noteColumn(int col) { int new_extra = col - visible_cols; @@ -101,8 +103,10 @@ try { return (Line) lines.elementAt(brow); } catch(ArrayIndexOutOfBoundsException x) { + /* DEBUG System.out.println("Buffer.lineAt(" +brow+ ") -> null\n");// NOI18N Thread.dumpStack(); + */ return null; } } Index: InterpANSI.java =================================================================== RCS file: /cvs/core/libsrc/org/netbeans/lib/terminalemulator/InterpANSI.java,v retrieving revision 1.6 diff -u -r1.6 InterpANSI.java --- InterpANSI.java 6 Nov 2001 02:14:41 -0000 1.6 +++ InterpANSI.java 9 Apr 2002 01:03:42 -0000 @@ -23,359 +23,333 @@ package org.netbeans.lib.terminalemulator; -/* OLD -import java.lang.Class; -import java.lang.reflect.Method; -*/ - public class InterpANSI extends InterpDumb { - protected InterpANSI(Ops ops) { - super(ops); - setup(); - } - - public String name() { - return "ansi"; // NOI18N - } - - public void reset() { - super.reset(); - } - - /* - * Management of number parsing - */ - - private static final int max_numbers = 5; - private int numberx = 0; - private String number[] = new String[max_numbers]; - - protected void resetNumber() { - for (int x = 0; x < max_numbers; x++) { - number[x] = new String(); - } - numberx = 0; - } - protected void remember_digit(char c) { - number[numberx] += c; - } - protected boolean pushNumber() { - numberx++; - return (numberx < max_numbers); - } - protected boolean noNumber() { - return number[0].equals(""); // NOI18N - } - protected int numberAt(int position) { - if (position > numberx) { - return 1; - } - return Integer.parseInt(number[position]); - } - protected int nNumbers() { - return numberx; - } - - - protected State st_esc = new State("esc"); // NOI18N - protected State st_esc_lb = new State("esc_lb"); // NOI18N - - private void setup() { - st_base.setAction((char) 27, st_esc, new ACT_TO_ESC()); - - st_esc.setRegular(st_esc, act_regular); - st_esc.setAction('7', st_base, new ACT_SC()); - st_esc.setAction('8', st_base, new ACT_RC()); - st_esc.setAction('c', st_base, new ACT_FULL_RESET()); - st_esc.setAction('[', st_esc_lb, act_reset_number); - - - st_esc_lb.setRegular(st_esc_lb, act_regular); - for (char c = '0'; c <= '9'; c++) - st_esc_lb.setAction(c, st_esc_lb, act_remember_digit); - st_esc_lb.setAction(';', st_esc_lb, new ACT_PUSH_NUMBER()); - st_esc_lb.setAction('A', st_base, new ACT_UP()); - st_esc_lb.setAction('B', st_base, new ACT_DO()); - st_esc_lb.setAction('C', st_base, new ACT_ND()); - st_esc_lb.setAction('D', st_base, new ACT_BC()); - st_esc_lb.setAction('H', st_base, new ACT_HO()); - st_esc_lb.setAction('i', st_base, new ACT_PRINT()); - st_esc_lb.setAction('J', st_base, new ACT_J()); - st_esc_lb.setAction('K', st_base, new ACT_K()); - st_esc_lb.setAction('L', st_base, new ACT_AL()); - st_esc_lb.setAction('M', st_base, new ACT_DL()); - st_esc_lb.setAction('m', st_base, new ACT_ATTR()); - st_esc_lb.setAction('n', st_base, new ACT_DSR()); - st_esc_lb.setAction('P', st_base, new ACT_DC()); - st_esc_lb.setAction('h', st_base, new ACT_SM()); - st_esc_lb.setAction('l', st_base, new ACT_RM()); - st_esc_lb.setAction('r', st_base, new ACT_MARGIN()); - st_esc_lb.setAction('t', st_base, new ACT_GLYPH()); - st_esc_lb.setAction('@', st_base, new ACT_IC()); - - state = st_base; - } - - - /* - * Actions .............................................................. - */ - - protected final class ACT_TO_ESC implements Actor { - public String action(char c) { - ctl_sequence = ""; // NOI18N - return null; + protected static class InterpTypeANSI extends InterpTypeDumb { + protected final State st_esc = new State("esc"); // NOI18N + protected final State st_esc_lb = new State("esc_lb"); // NOI18N + + protected final Actor act_reset_number = new ACT_RESET_NUMBER(); + protected final Actor act_remember_digit = new ACT_REMEMBER_DIGIT(); + + protected InterpTypeANSI() { + st_base.setAction((char) 27, st_esc, new ACT_TO_ESC()); + + st_esc.setRegular(st_esc, act_regular); + st_esc.setAction('7', st_base, new ACT_SC()); + st_esc.setAction('8', st_base, new ACT_RC()); + st_esc.setAction('c', st_base, new ACT_FULL_RESET()); + st_esc.setAction('[', st_esc_lb, act_reset_number); + + + st_esc_lb.setRegular(st_esc_lb, act_regular); + for (char c = '0'; c <= '9'; c++) + st_esc_lb.setAction(c, st_esc_lb, act_remember_digit); + st_esc_lb.setAction(';', st_esc_lb, new ACT_PUSH_NUMBER()); + st_esc_lb.setAction('A', st_base, new ACT_UP()); + st_esc_lb.setAction('B', st_base, new ACT_DO()); + st_esc_lb.setAction('C', st_base, new ACT_ND()); + st_esc_lb.setAction('D', st_base, new ACT_BC()); + st_esc_lb.setAction('H', st_base, new ACT_HO()); + st_esc_lb.setAction('i', st_base, new ACT_PRINT()); + st_esc_lb.setAction('J', st_base, new ACT_J()); + st_esc_lb.setAction('K', st_base, new ACT_K()); + st_esc_lb.setAction('L', st_base, new ACT_AL()); + st_esc_lb.setAction('M', st_base, new ACT_DL()); + st_esc_lb.setAction('m', st_base, new ACT_ATTR()); + st_esc_lb.setAction('n', st_base, new ACT_DSR()); + st_esc_lb.setAction('P', st_base, new ACT_DC()); + st_esc_lb.setAction('h', st_base, new ACT_SM()); + st_esc_lb.setAction('l', st_base, new ACT_RM()); + st_esc_lb.setAction('r', st_base, new ACT_MARGIN()); + st_esc_lb.setAction('t', st_base, new ACT_GLYPH()); + st_esc_lb.setAction('@', st_base, new ACT_IC()); + } + + protected final class ACT_TO_ESC implements Actor { + public String action(AbstractInterp ai, char c) { + InterpDumb i = (InterpDumb) ai; + i.ctl_sequence = ""; // NOI18N + return null; + } } - } - protected final class ACT_SC implements Actor { - public String action(char c) { - ops.op_sc(); - return null; + protected final class ACT_SC implements Actor { + public String action(AbstractInterp ai, char c) { + ai.ops.op_sc(); + return null; + } } - } - protected final class ACT_RC implements Actor { - public String action(char c) { - ops.op_rc(); - return null; + protected final class ACT_RC implements Actor { + public String action(AbstractInterp ai, char c) { + ai.ops.op_rc(); + return null; + } } - } - protected final class ACT_FULL_RESET implements Actor { - public String action(char c) { - ops.op_full_reset(); - return null; + protected final class ACT_FULL_RESET implements Actor { + public String action(AbstractInterp ai, char c) { + ai.ops.op_full_reset(); + return null; + } } - } - protected Actor act_reset_number = new Actor() { - public String action(char c) { - resetNumber(); - return null; - } - }; - - protected Actor act_remember_digit = new Actor() { - public String action(char c) { - remember_digit(c); - return null; - } - }; - - protected final class ACT_PUSH_NUMBER implements Actor { - public String action(char c) { - if (!pushNumber()) - return "ACT PUSH_NUMBER"; // NOI18N - return null; + protected class ACT_RESET_NUMBER implements Actor { + public String action(AbstractInterp ai, char c) { + ai.resetNumber(); + return null; + } + }; + + protected class ACT_REMEMBER_DIGIT implements Actor { + public String action(AbstractInterp ai, char c) { + ai.remember_digit(c); + return null; + } + }; + + protected final class ACT_PUSH_NUMBER implements Actor { + public String action(AbstractInterp ai, char c) { + if (!ai.pushNumber()) + return "ACT PUSH_NUMBER"; // NOI18N + return null; + } } - } - protected final class ACT_UP implements Actor { - public String action(char c) { - if (noNumber()) - ops.op_up(1); - else - ops.op_up(numberAt(0)); - return null; + protected final class ACT_UP implements Actor { + public String action(AbstractInterp ai, char c) { + if (ai.noNumber()) + ai.ops.op_up(1); + else + ai.ops.op_up(ai.numberAt(0)); + return null; + } } - } - protected final class ACT_DO implements Actor { - public String action(char c) { - if (noNumber()) - ops.op_do(1); - else - ops.op_do(numberAt(0)); - return null; + protected final class ACT_DO implements Actor { + public String action(AbstractInterp ai, char c) { + if (ai.noNumber()) + ai.ops.op_do(1); + else + ai.ops.op_do(ai.numberAt(0)); + return null; + } } - } - protected final class ACT_ND implements Actor { - public String action(char c) { - if (noNumber()) - ops.op_nd(1); - else - ops.op_nd(numberAt(0)); - return null; + protected final class ACT_ND implements Actor { + public String action(AbstractInterp ai, char c) { + if (ai.noNumber()) + ai.ops.op_nd(1); + else + ai.ops.op_nd(ai.numberAt(0)); + return null; + } } - } - protected final class ACT_BC implements Actor { - public String action(char c) { - if (noNumber()) - ops.op_bc(1); - else - ops.op_bc(numberAt(0)); - return null; + protected final class ACT_BC implements Actor { + public String action(AbstractInterp ai, char c) { + if (ai.noNumber()) + ai.ops.op_bc(1); + else + ai.ops.op_bc(ai.numberAt(0)); + return null; + } } - } - protected final class ACT_MARGIN implements Actor { - public String action(char c) { - if (noNumber()) - ops.op_margin(0, 0); - else - ops.op_margin(numberAt(0), numberAt(1)); - return null; + protected final class ACT_MARGIN implements Actor { + public String action(AbstractInterp ai, char c) { + if (ai.noNumber()) + ai.ops.op_margin(0, 0); + else + ai.ops.op_margin(ai.numberAt(0), ai.numberAt(1)); + return null; + } } - } - protected final class ACT_DC implements Actor { - public String action(char c) { - if (noNumber()) - ops.op_dc(1); - else - ops.op_dc(numberAt(0)); - return null; + protected final class ACT_DC implements Actor { + public String action(AbstractInterp ai, char c) { + if (ai.noNumber()) + ai.ops.op_dc(1); + else + ai.ops.op_dc(ai.numberAt(0)); + return null; + } } - } - protected final class ACT_SM implements Actor { - public String action(char c) { - if (noNumber()) - ops.op_set_mode(1); - else - ops.op_set_mode(numberAt(0)); - return null; + protected final class ACT_SM implements Actor { + public String action(AbstractInterp ai, char c) { + if (ai.noNumber()) + ai.ops.op_set_mode(1); + else + ai.ops.op_set_mode(ai.numberAt(0)); + return null; + } } - } - protected final class ACT_RM implements Actor { - public String action(char c) { - if (noNumber()) - ops.op_reset_mode(1); - else - ops.op_reset_mode(numberAt(0)); - return null; + protected final class ACT_RM implements Actor { + public String action(AbstractInterp ai, char c) { + if (ai.noNumber()) + ai.ops.op_reset_mode(1); + else + ai.ops.op_reset_mode(ai.numberAt(0)); + return null; + } } - } - protected final class ACT_IC implements Actor { - public String action(char c) { - if (noNumber()) - ops.op_ic(1); - else - ops.op_ic(numberAt(0)); - return null; - } - } - protected final class ACT_DL implements Actor { - public String action(char c) { - if (noNumber()) { - ops.op_dl(1); - } else { - ops.op_dl(numberAt(0)); - } - return null; + protected final class ACT_IC implements Actor { + public String action(AbstractInterp ai, char c) { + if (ai.noNumber()) + ai.ops.op_ic(1); + else + ai.ops.op_ic(ai.numberAt(0)); + return null; + } } - } - protected final class ACT_HO implements Actor { - public String action(char c) { - if (noNumber()) { - ops.op_ho(); - } else { - ops.op_cm(numberAt(0), numberAt(1)); // row, col - } - return null; + protected final class ACT_DL implements Actor { + public String action(AbstractInterp ai, char c) { + if (ai.noNumber()) { + ai.ops.op_dl(1); + } else { + ai.ops.op_dl(ai.numberAt(0)); + } + return null; + } } - } - protected final class ACT_PRINT implements Actor { - public String action(char c) { - // Ignored for now, except for 'dump time' - if (noNumber()) { - // Print screen - } else { - switch (numberAt(0)) { - case 1: // Print Line - case 4: // Stop Print Log - case 5: // Start Print Log - break; - case 10: - ops.op_time(true); - break; - case 11: - ops.op_time(false); - break; + protected final class ACT_HO implements Actor { + public String action(AbstractInterp ai, char c) { + if (ai.noNumber()) { + ai.ops.op_ho(); + } else { + ai.ops.op_cm(ai.numberAt(0), ai.numberAt(1));// row, col } + return null; } - return null; } - } - protected final class ACT_J implements Actor { - public String action(char c) { - if (noNumber()) { - ops.op_cd(); - } else { - int count = numberAt(0); - if (count == 1) { - return "ACT J: count of 1 not supported"; // NOI18N - } else if (count == 2) { - ops.op_cl(); + protected final class ACT_PRINT implements Actor { + public String action(AbstractInterp ai, char c) { + // Ignored for now, except for 'dump time' + if (ai.noNumber()) { + // Print screen + } else { + switch (ai.numberAt(0)) { + case 1: // Print Line + case 4: // Stop Print Log + case 5: // Start Print Log + break; + case 10: + ai.ops.op_time(true); + break; + case 11: + ai.ops.op_time(false); + break; + } + } + return null; + } + } + protected final class ACT_J implements Actor { + public String action(AbstractInterp ai, char c) { + if (ai.noNumber()) { + ai.ops.op_cd(); + } else { + int count = ai.numberAt(0); + if (count == 1) { + return "ACT J: count of 1 not supported"; // NOI18N + } else if (count == 2) { + ai.ops.op_cl(); + } } - } - return null; + return null; + } } - } - protected final class ACT_K implements Actor { - public String action(char c) { - if (noNumber()) { - ops.op_ce(); - } else { - int count = numberAt(0); - if (count == 1) { - return "ACT K: count of 1 not supported"; // NOI18N - } else if (count == 2) { - return "ACT K: count of 2 not supported"; // NOI18N + protected final class ACT_K implements Actor { + public String action(AbstractInterp ai, char c) { + if (ai.noNumber()) { + ai.ops.op_ce(); + } else { + int count = ai.numberAt(0); + if (count == 1) { + return "ACT K: count of 1 not supported"; // NOI18N + } else if (count == 2) { + return "ACT K: count of 2 not supported"; // NOI18N + } } - } - return null; + return null; + } } - } - protected final class ACT_AL implements Actor { - public String action(char c) { - if (noNumber()) { - ops.op_al(1); - } else { - ops.op_al(numberAt(0)); - } - return null; + protected final class ACT_AL implements Actor { + public String action(AbstractInterp ai, char c) { + if (ai.noNumber()) { + ai.ops.op_al(1); + } else { + ai.ops.op_al(ai.numberAt(0)); + } + return null; + } } - } - protected final class ACT_ATTR implements Actor { - public String action(char c) { - // set graphics modes (bold, reverse video etc) - if (noNumber()) { - ops.op_attr(0); // reset everything - } else { - for (int n = 0; n <= nNumbers(); n++) - ops.op_attr(numberAt(n)); + protected final class ACT_ATTR implements Actor { + public String action(AbstractInterp ai, char c) { + // set graphics modes (bold, reverse video etc) + if (ai.noNumber()) { + ai.ops.op_attr(0); // reset everything + } else { + for (int n = 0; n <= ai.nNumbers(); n++) + ai.ops.op_attr(ai.numberAt(n)); + } + return null; } - return null; } - } - protected final class ACT_DSR implements Actor { - // Device Status Report - public String action(char c) { - if (noNumber()) { - ops.op_status_report(5); // reset everything - } else { - ops.op_status_report(numberAt(0)); + protected final class ACT_DSR implements Actor { + // Device Status Report + public String action(AbstractInterp ai, char c) { + if (ai.noNumber()) { + ai.ops.op_status_report(5); // reset everything + } else { + ai.ops.op_status_report(ai.numberAt(0)); + } + return null; } - return null; } - } - protected final class ACT_GLYPH implements Actor { - public String action(char c) { - if (noNumber()) { - return "ACT GLYPH: missing number"; // NOI18N - } else { - int p1 = numberAt(0); - int p2 = numberAt(1); - int p3 = numberAt(2); - if (p1 == 22) { - ops.op_glyph(p2, p3); + protected final class ACT_GLYPH implements Actor { + public String action(AbstractInterp ai, char c) { + if (ai.noNumber()) { + return "ACT GLYPH: missing number"; // NOI18N } else { - return "ACT GLYPH: op othger than 22 not supported"; // NOI18N + int p1 = ai.numberAt(0); + int p2 = ai.numberAt(1); + int p3 = ai.numberAt(2); + if (p1 == 22) { + ai.ops.op_glyph(p2, p3); + } else { + return "ACT GLYPH: op othger than 22 not supported"; // NOI18N + } } - } - return null; + return null; + } } + } + + private InterpTypeANSI type; + + public static final InterpTypeANSI type_singleton = new InterpTypeANSI(); + + public InterpANSI(Ops ops) { + super(ops, type_singleton); + this.type = type_singleton; + setup(); + } + + protected InterpANSI(Ops ops, InterpTypeANSI type) { + super(ops, type); + this.type = type; + setup(); + } + + public String name() { + return "ansi"; // NOI18N + } + + public void reset() { + super.reset(); + } + + private void setup() { + state = type.st_base; } } Index: InterpDtTerm.java =================================================================== RCS file: /cvs/core/libsrc/org/netbeans/lib/terminalemulator/InterpDtTerm.java,v retrieving revision 1.4 diff -u -r1.4 InterpDtTerm.java --- InterpDtTerm.java 6 Nov 2001 02:14:41 -0000 1.4 +++ InterpDtTerm.java 9 Apr 2002 01:03:42 -0000 @@ -28,8 +28,145 @@ class InterpDtTerm extends InterpANSI { - protected InterpDtTerm(Ops ops) { - super(ops); + protected static class InterpTypeDtTerm extends InterpTypeANSI { + protected final State st_esc_rb = new State("esc_rb"); // NOI18N + protected final State st_esc_lb_q = new State("esc_lb_q");// NOI18N + protected final State st_esc_lb_b = new State("esc_lb_b");// NOI18N + protected final State st_wait = new State("wait"); // NOI18N + + protected final Actor act_DEC_private = new ACT_DEC_PRIVATE(); + protected final Actor act_M = new ACT_M(); + protected final Actor act_D = new ACT_D(); + protected final Actor act_done_collect = new ACT_DONE_COLLECT(); + protected final Actor act_collect = new ACT_COLLECT(); + protected final Actor act_start_collect = new ACT_START_COLLECT(); + + + protected InterpTypeDtTerm() { + st_esc.setAction(']', st_esc_rb, act_start_collect); + + // the following two may be generic ANSI escapes + st_esc.setAction('D', st_base, act_D); + st_esc.setAction('M', st_base, act_M); + + for (char c = 0; c < 128; c++) + st_esc_rb.setAction(c, st_esc_rb, act_collect); + st_esc_rb.setAction((char) 27, st_wait, act_nop); + + st_wait.setAction('\\', st_base, act_done_collect); + + st_esc_lb.setAction('?', st_esc_lb_q, act_reset_number); + + for (char c = '0'; c <= '9'; c++) + st_esc_lb_q.setAction(c, st_esc_lb_q, act_remember_digit); + st_esc_lb_q.setAction('h', st_base, act_DEC_private); + st_esc_lb_q.setAction('l', st_base, act_DEC_private); + st_esc_lb_q.setAction('r', st_base, act_DEC_private); + st_esc_lb_q.setAction('s', st_base, act_DEC_private); + + st_esc_lb.setAction('!', st_esc_lb_b, act_reset_number); + st_esc_lb_b.setAction('p', st_base, new ACT_DEC_STR()); + } + + protected final class ACT_START_COLLECT implements Actor { + public String action(AbstractInterp ai, char c) { + InterpDtTerm i = (InterpDtTerm) ai; + i.text = ""; // NOI18N + return null; + } + } + + protected final class ACT_COLLECT implements Actor { + public String action(AbstractInterp ai, char c) { + // java bug 4318526 text += c; + InterpDtTerm i = (InterpDtTerm) ai; + i.text = i.text + c; + return null; + } + } + + protected final class ACT_DONE_COLLECT implements Actor { + public String action(AbstractInterp ai, char c) { + /* DEBUG + System.out.println("DtTerm emulation: got '" + text + "'"); // NOI18N + */ + return null; + } + } + + protected final class ACT_D implements Actor { + public String action(AbstractInterp ai, char c) { + ai.ops.op_do(1); + return null; + } + }; + + protected final class ACT_M implements Actor { + public String action(AbstractInterp ai, char c) { + ai.ops.op_up(1); + return null; + } + } + + protected final class ACT_DEC_PRIVATE implements Actor { + public String action(AbstractInterp ai, char c) { + if (ai.noNumber()) + return "act_DEC_private: no number"; // NOI18N + int n = ai.numberAt(0); + switch(c) { + case 'h': + if (n == 5) + ai.ops.op_reverse(true); + else if (n == 25) + ai.ops.op_cursor_visible(true); + else + return "act_DEC_private: unrecognized cmd " + c; // NOI18N + break; + case 'l': + if (n == 5) + ai.ops.op_reverse(false); + else if (n == 25) + ai.ops.op_cursor_visible(false); + else + return "act_DEC_private: unrecognized cmd " + c; // NOI18N + break; + case 'r': + case 's': + /* DEBUG + System.out.println("act_DEC_private " + // NOI18N + numberAt(0) + " " + c); // NOI18N + */ + break; + default: + return "act_DEC_private: unrecognized cmd " + c; // NOI18N + } + return null; + } + } + + protected final class ACT_DEC_STR implements Actor { + public String action(AbstractInterp ai, char c) { + ai.ops.op_soft_reset(); + return null; + } + } + } + + private String text = null; + + private InterpTypeDtTerm type; + + public static final InterpTypeDtTerm type_singleton = new InterpTypeDtTerm(); + + public InterpDtTerm(Ops ops) { + super(ops, type_singleton); + this.type = type_singleton; + setup(); + } + + protected InterpDtTerm(Ops ops, InterpTypeDtTerm type) { + super(ops, type); + this.type = type; setup(); } @@ -42,116 +179,9 @@ text = null; } - protected State st_esc_rb = new State("esc_rb"); // NOI18N - protected State st_esc_lb_q = new State("esc_lb_q");// NOI18N - protected State st_esc_lb_b = new State("esc_lb_b");// NOI18N - protected State st_wait = new State("wait"); // NOI18N - - private String text = null; private void setup() { - st_esc.setAction(']', st_esc_rb, act_start_collect); - - // the following two may be generic ANSI escapes - st_esc.setAction('D', st_base, act_D); - st_esc.setAction('M', st_base, act_M); - - for (char c = 0; c < 128; c++) - st_esc_rb.setAction(c, st_esc_rb, act_collect); - st_esc_rb.setAction((char) 27, st_wait, act_nop); - - st_wait.setAction('\\', st_base, act_done_collect); - - st_esc_lb.setAction('?', st_esc_lb_q, act_reset_number); - - for (char c = '0'; c <= '9'; c++) - st_esc_lb_q.setAction(c, st_esc_lb_q, act_remember_digit); - st_esc_lb_q.setAction('h', st_base, act_DEC_private); - st_esc_lb_q.setAction('l', st_base, act_DEC_private); - st_esc_lb_q.setAction('r', st_base, act_DEC_private); - st_esc_lb_q.setAction('s', st_base, act_DEC_private); - - st_esc_lb.setAction('!', st_esc_lb_b, act_reset_number); - st_esc_lb_b.setAction('p', st_base, new ACT_DEC_STR()); - - state = st_base; + state = type.st_base; } - protected final Actor act_start_collect = new Actor() { - public String action(char c) { - text = ""; // NOI18N - return null; - } - }; - - protected final Actor act_collect = new Actor() { - public String action(char c) { - // java bug 4318526 text += c; - text = text + c; - return null; - } - }; - - protected final Actor act_done_collect = new Actor() { - public String action(char c) { - // System.out.println("DtTerm emulation: got '" + text + "'"); // NOI18N - return null; - } - }; - - protected final Actor act_D = new Actor() { - public String action(char c) { - ops.op_do(1); - return null; - } - }; - - protected final Actor act_M = new Actor() { - public String action(char c) { - ops.op_up(1); - return null; - } - }; - - protected final Actor act_DEC_private = new Actor() { - public String action(char c) { - if (noNumber()) - return "act_DEC_private: no number"; // NOI18N - int n = numberAt(0); - switch(c) { - case 'h': - if (n == 5) - ops.op_reverse(true); - else if (n == 25) - ops.op_cursor_visible(true); - else - return "act_DEC_private: unrecognized cmd " + c; // NOI18N - break; - case 'l': - if (n == 5) - ops.op_reverse(false); - else if (n == 25) - ops.op_cursor_visible(false); - else - return "act_DEC_private: unrecognized cmd " + c; // NOI18N - break; - case 'r': - case 's': - /* - System.out.println("act_DEC_private " + - numberAt(0) + " " + c); - */ - break; - default: - return "act_DEC_private: unrecognized cmd " + c; // NOI18N - } - return null; - } - }; - protected final class ACT_DEC_STR implements Actor { - public String action(char c) { - ops.op_soft_reset(); - return null; - } - } } Index: InterpDumb.java =================================================================== RCS file: /cvs/core/libsrc/org/netbeans/lib/terminalemulator/InterpDumb.java,v retrieving revision 1.6 diff -u -r1.6 InterpDumb.java --- InterpDumb.java 2 Nov 2001 08:33:27 -0000 1.6 +++ InterpDumb.java 9 Apr 2002 01:03:43 -0000 @@ -23,15 +23,136 @@ package org.netbeans.lib.terminalemulator; -/* OLD -import java.lang.Class; -import java.lang.reflect.Method; -*/ +import java.util.Stack; public class InterpDumb extends AbstractInterp { - protected InterpDumb(Ops ops) { + protected static class InterpTypeDumb { + public final State st_base = new State("base"); // NOI18N + + protected final Actor act_nop = new ACT_NOP(); + protected final Actor act_pause = new ACT_PAUSE(); + protected final Actor act_err = new ACT_ERR(); + protected final Actor act_regular = new ACT_REGULAR(); + protected final Actor act_cr = new ACT_CR(); + protected final Actor act_lf = new ACT_LF(); + protected final Actor act_bs = new ACT_BS(); + protected final Actor act_tab = new ACT_TAB(); + protected final Actor act_beL = new ACT_BEL(); + + protected InterpTypeDumb() { + st_base.setRegular(st_base, act_regular); + + for (char c = 0; c < 128; c++) + st_base.setAction(c, st_base, act_regular); + + st_base.setAction((char) 0, st_base, act_pause); + st_base.setAction('\r', st_base, act_cr); + st_base.setAction('\n', st_base, act_lf); + st_base.setAction('\b', st_base, act_bs); + st_base.setAction('\t', st_base, act_tab); + st_base.setAction((char) 7, st_base, act_beL); + } + + protected final class ACT_NOP implements Actor { + public String action(AbstractInterp ai, char c) { + return null; + } + }; + + protected final class ACT_PAUSE implements Actor { + public String action(AbstractInterp ai, char c) { + ai.ops.op_pause(); + return null; + } + }; + + protected final class ACT_ERR implements Actor { + public String action(AbstractInterp ai, char c) { + return "ACT ERROR"; // NOI18N + } + }; + + protected final class ACT_REGULAR implements Actor { + public String action(AbstractInterp ai, char c) { + ai.ops.op_char(c); + return null; + } + }; + + + protected final class ACT_CR implements Actor { + public String action(AbstractInterp ai, char c) { + ai.ops.op_carriage_return(); + return null; + } + }; + + protected final class ACT_LF implements Actor { + public String action(AbstractInterp ai, char c) { + ai.ops.op_line_feed(); + return null; + } + }; + + + protected final class ACT_BS implements Actor { + public String action(AbstractInterp ai, char c) { + ai.ops.op_back_space(); + return null; + } + }; + + protected final class ACT_TAB implements Actor { + public String action(AbstractInterp ai, char c) { + ai.ops.op_tab(); + return null; + } + }; + + protected final class ACT_BEL implements Actor { + public String action(AbstractInterp ai, char c) { + ai.ops.op_bel(); + return null; + } + } + } + + /* + * A stack for State + */ + private Stack stack = new Stack(); + + protected void push_state(State s) { + stack.push(s); + } + + protected State pop_state() { + return (State) stack.pop(); + } + + protected void pop_all_states() { + while(!stack.empty()) + stack.pop(); + } + + + protected String ctl_sequence = null; + + private InterpTypeDumb type; + + public static final InterpTypeDumb type_singleton = new InterpTypeDumb(); + + public InterpDumb(Ops ops) { + super(ops); + this.type = type_singleton; + setup(); + ctl_sequence = null; + } + + protected InterpDumb(Ops ops, InterpTypeDumb type) { super(ops); + this.type = type; setup(); ctl_sequence = null; } @@ -42,32 +163,19 @@ public void reset() { super.reset(); - state = st_base; + pop_all_states(); + state = type.st_base; ctl_sequence = null; } - protected State st_base = new State("base"); // NOI18N - protected String ctl_sequence = null; - private void setup() { - st_base.setRegular(st_base, act_regular); - - for (char c = 0; c < 128; c++) - st_base.setAction(c, st_base, act_regular); - - st_base.setAction((char) 0, st_base, act_pause); - st_base.setAction('\r', st_base, act_cr); - st_base.setAction('\n', st_base, act_lf); - st_base.setAction('\b', st_base, act_bs); - st_base.setAction('\t', st_base, act_tab); - st_base.setAction((char) 7, st_base, act_beL); - - state = st_base; + state = type.st_base; } private void reset_state_bad() { /* + DEBUG System.out.println("Unrecognized sequence in state " + state.name()); // NOI18N if (ctl_sequence != null) { for (int sx = 0; sx < ctl_sequence.length(); sx++) @@ -77,9 +185,9 @@ for (int sx = 0; sx < ctl_sequence.length(); sx++) System.out.print(ctl_sequence.charAt(sx) + " "); // NOI18N - System.out.println(); + System.out.println(); // NOI18N } - */ + */ reset(); } @@ -91,17 +199,32 @@ try { State.Action a = state.getAction(c); - String err_str = a.actor.action(c); + /* DEBUG + if (a == null) { + System.out.println("null action in state " + state.name() + // NOI18N + " for char " + c + " = " + (int) c); // NOI18N + } + if (a.actor == null) { + System.out.println("null a.actor in state " + state.name() + // NOI18N + " for char " + c + " = " + (int) c); // NOI18N + } + */ + String err_str = a.actor.action(this, c); if (err_str != null) { - // System.out.println("action error: " + err_str); // NOI18N + /* DEBUG + System.out.println("action error: " + err_str); // NOI18N + */ reset_state_bad(); return; } - state = a.new_state; + if (a.new_state != null) + state = a.new_state; + else + ; // must be set by action, usually using pop_state() } finally { - if (state == st_base) + if (state == type.st_base) ctl_sequence = null; } } @@ -110,64 +233,4 @@ * Actions .............................................................. */ - protected final Actor act_nop = new Actor() { - public String action(char c) { - return null; - } - }; - - protected final Actor act_pause = new Actor() { - public String action(char c) { - ops.op_pause(); - return null; - } - }; - - protected final Actor act_err = new Actor() { - public String action(char c) { - return "ACT ERROR"; // NOI18N - } - }; - - protected final Actor act_regular = new Actor() { - public String action(char c) { - ops.op_char(c); - return null; - } - }; - - protected final Actor act_cr = new Actor() { - public String action(char c) { - ops.op_carriage_return(); - return null; - } - }; - - protected final Actor act_lf = new Actor() { - public String action(char c) { - ops.op_line_feed(); - return null; - } - }; - - protected final Actor act_bs = new Actor() { - public String action(char c) { - ops.op_back_space(); - return null; - } - }; - - protected final Actor act_tab = new Actor() { - public String action(char c) { - ops.op_tab(); - return null; - } - }; - - protected final Actor act_beL = new Actor() { - public String action(char c) { - ops.op_bel(); - return null; - } - }; } Index: InterpKit.java =================================================================== RCS file: /cvs/core/libsrc/org/netbeans/lib/terminalemulator/InterpKit.java,v retrieving revision 1.3 diff -u -r1.3 InterpKit.java --- InterpKit.java 4 Oct 2001 09:59:59 -0000 1.3 +++ InterpKit.java 9 Apr 2002 01:03:43 -0000 @@ -36,7 +36,7 @@ else if (name.equals("ansi")) // NOI18N return new InterpANSI(ops); else if (name.equals("dtterm")) // NOI18N - return new InterpDtTerm(ops); + return new InterpDtTerm(ops); else return null; } Index: Line.java =================================================================== RCS file: /cvs/core/libsrc/org/netbeans/lib/terminalemulator/Line.java,v retrieving revision 1.3 diff -u -r1.3 Line.java --- Line.java 4 Oct 2001 09:59:59 -0000 1.3 +++ Line.java 9 Apr 2002 01:03:43 -0000 @@ -26,8 +26,9 @@ public int glyph_rendition; // Background color for the whole line // This is independent of per-character // rendition. - private char buf[]; - private int attr[]; + + private char buf[]; // actual characters + private int attr[]; // attributes (allocated on demand) // SHOULD use shorts? private int capacity; // == buf.length == attr.length @@ -105,11 +106,55 @@ public char [] charArray() { return buf; } + public int [] attrArray() { return attr; } + public byte width(MyFontMetrics metrics, int at) { + if (at >= capacity) + return 1; + return (byte) metrics.wcwidth(buf[at]); + } + + /* + * Convert a cell column to a buffer column. + */ + public int cellToBuf(MyFontMetrics metrics, int target_col) { + if (metrics.isMultiCell()) { + int bc = 0; + int vc = 0; + for(;;) { + int w = width(metrics, bc); + if (vc + w - 1 >= target_col) + break; + vc += w; + bc++; + } + return bc; + } else { + return target_col; + } + } + + /* + * Convert a buffer column to a cell column. + */ + public int bufToCell(MyFontMetrics metrics, int target_col) { + if (metrics.isMultiCell()) { + int vc = 0; + for (int bc = 0; bc < target_col; bc++) { + vc += width(metrics, bc); + } + return vc; + } else { + return target_col; + } + } + + + public StringBuffer stringBuffer() { // only used for word finding // Grrr, why don't we have: new StringBuffer(buf, 0, length); @@ -118,11 +163,11 @@ } /* - * Ensure that our capacity is min_capacity rounded up to 8. + * Ensure that we have at least 'min_capacity'. */ - private void ensureCapacity(Buffer lbuf, int min_capacity) { + private void ensureCapacity(Term term, int min_capacity) { - lbuf.noteColumn(min_capacity); + term.noteColumn(this, min_capacity); if (min_capacity <= capacity) return; // nothing to do @@ -163,11 +208,18 @@ * Insert character and attribute at 'column' and shift everything * past 'column' right. */ - public void insertCharAt(Buffer lbuf, char c, int column, int a) { + public void insertCharAt(Term term, char c, int column, int a) { int new_length = length + 1; - ensureCapacity(lbuf, new_length); + ensureCapacity(term, new_length); + + if (column >= length) { + // fill any newly opened gap (between length and column) with SP + for (int fx = length; fx < column; fx++) + buf[fx] = ' '; + } System.arraycopy(buf, column, buf, column + 1, length - column); + term.checkForMultiCell(c); buf[column] = c; if (haveAttributes(a)) { @@ -182,11 +234,15 @@ * Generic addition and modification. * Line will grow to accomodate column. */ - public void setCharAt(Buffer lbuf, char c, int column, int a) { + public void setCharAt(Term term, char c, int column, int a) { if (column >= length) { - ensureCapacity(lbuf, column+1); + ensureCapacity(term, column+1); + // fill any newly opened gap (between length and column) with SP + for (int fx = length; fx < column; fx++) + buf[fx] = ' '; length = column+1; } + term.checkForMultiCell(c); buf[column] = c; if (haveAttributes(a)) { attr[column] = a; @@ -206,15 +262,15 @@ length--; } - public void clearToEndFrom(Buffer lbuf, int col) { - ensureCapacity(lbuf, col+1); + public void clearToEndFrom(Term term, int col) { + ensureCapacity(term, col+1); // Grrr, why is there a System.arrayCopy() but no System.arrayClear()? for (int cx = col; cx < length; cx++) - buf[cx] = 0; + buf[cx] = ' '; if (attr != null) { for (int cx = col; cx < length; cx++) - attr[cx] = 0; + attr[cx] = ' '; } length = col; } @@ -225,8 +281,10 @@ * If the ecol is past the actual line length a "\n" is appended. */ public String text(int bcol, int ecol) { - // System.out.println("Line.text(bcol " + bcol + " ecol " + ecol + ")"); // NOI18N - // System.out.println("\tlength " + length); // NOI18N + /* DEBUG + System.out.println("Line.text(bcol " + bcol + " ecol " + ecol + ")"); // NOI18N + System.out.println("\tlength " + length); // NOI18N + */ String newline = ""; // NOI18N Index: LineDiscipline.java =================================================================== RCS file: /cvs/core/libsrc/org/netbeans/lib/terminalemulator/LineDiscipline.java,v retrieving revision 1.4.14.1 diff -u -r1.4.14.1 LineDiscipline.java --- LineDiscipline.java 7 Dec 2001 12:38:12 -0000 1.4.14.1 +++ LineDiscipline.java 9 Apr 2002 01:03:44 -0000 @@ -167,8 +167,41 @@ if (nchars == 0) return; // nothing left to BS over + char erased_char = ' '; // The char we're going to erase + try { + erased_char = line.charAt(nchars-1); + } catch (Exception x) { + return; // apparently the 'nchars == 0' test failed above ;-) + } + int cwidth = getTerm().charWidth(erased_char); + // remove from line buffer line.delete(nchars-1, nchars); + + // HACK + // If you play a bit with DtTerm on Solaris in a non-latin locale + // you'll see that when you BS over a multi-cell character it + // doesn't erase the whole character. The character is erased but the + // cursor moves back only one column. So you usually need to BS twice + // to get rid of it. If you "fix" Term to do something more reasonable + // you'll find out that as you backspace you'll run over the cursor. + // that's because the kernel linebuffer accounting assumes the above setup. + // I"m not sure how all of this came about but we have to mimic similar + // acounting and we do it by padding the buffer (only) with a bunch of spaces. + // + // NOTE: There are two strong invariants you have to keep in mind: + // - Solaris, and I assume other unixes, stick to the BS-SP-BS echo + // even though they seem to know about character widths. + // - BS from Term's point of view is _strictly_ a cursor motion operation! + // The fact that it erases things has to do with the line discipline + // (kernels or this class 'ere) + // + // Now I know non-unix people will want BS to behave sanely in non-unix + // environments, so perhapws we SHOULD have a property to control whether + // things get erased the unix way or some other way. + + while(--cwidth > 0 ) + line.append(' '); // erase character on screen toDTE.putChars(bs_sequence, 0, 3); Index: MyFontMetrics.java =================================================================== RCS file: /cvs/core/libsrc/org/netbeans/lib/terminalemulator/MyFontMetrics.java,v retrieving revision 1.2 diff -u -r1.2 MyFontMetrics.java --- MyFontMetrics.java 24 Aug 2001 13:46:55 -0000 1.2 +++ MyFontMetrics.java 9 Apr 2002 01:03:44 -0000 @@ -22,10 +22,107 @@ package org.netbeans.lib.terminalemulator; import java.awt.*; +import java.util.AbstractMap; +import java.util.HashMap; class MyFontMetrics { + + /** + * WidthCache contains a byte array that maps a character to it's cell width. + * It also keeps track of whether we've discovered that we're dealing with a + * font that has wide characters. This information is kept with WidthCache + * because the caches are shared between MyFontMetrics and we test for + * multi-cellnes only on a cache miss. So we got into a situation where one + * Term got a wide character, missed the cache, figured that it's multi-cell, + * then another Term got the same character didn't miss the cache and didn't + * set it's own multi-cell bit. + * + * The reference counting stuff is explained with CacheFactory. + */ + + static class WidthCache { + byte [] cache = new byte[Character.MAX_VALUE]; + int reference_count = 1; + + public void up() { + reference_count ++; + if (reference_count == 1) + cache = new byte[Character.MAX_VALUE]; + } + + public void down() { + if (reference_count == 0) + return; + reference_count --; + if (reference_count == 0) + cache = null; + } + + public boolean isMultiCell() { + return multiCell; + } + public void setMultiCell(boolean multiCell) { + if (!this.multiCell && multiCell) { + // first time + /* DEBUG + System.out.println("##########################################"); // NOI18N + System.out.println("# SWITCHING TO MULTI CELL #"); // NOI18N + System.out.println("##########################################"); // NOI18N + */ + } + this.multiCell = multiCell; + } + private boolean multiCell = false; + } + + /** + * + * CacheFactory doles out WidthCaches. + * + * These caches are 64Kb (Character.MAX_VALUE) and we don't really want + * Each interp to have it's own. So we share them in a map using FontMetrics + * as a key. Unfortunately stuff will accumulate in the map. A WeakHashMap + * is no good because the keys (FontMetrics) are usually alive. For all I + * know Jave might be cacheing them in turn. I actually tried using a + * WeakHashMap and wouldn't see things going away, even after System.gc(). + *

+ * So we get this slightly more involved manager. + *
+ * A WidthCache holds on to the actual WidthCache and reference counts it. + * When the count goes to 0 the actual cache array is "free"d be nulling + * it's reference. To make the reference count go down CacheFactory.disposeBy() + * is used. And that is called from MyFontMetrics.finalize(). + * + * NOTE: The actual WidthCache's instances _will_ accumulate, but they are small and + * there are only so many font variations an app can go through. As I + * mentioned above using a WeakHashMap doesn't help much because WidthCache's + * are keyed by relatively persistent FontMetrics. + */ + + private static class CacheFactory { + static synchronized WidthCache cacheForFontMetrics(FontMetrics fm) { + WidthCache entry = (WidthCache) map.get(fm); + if (entry == null) { + entry = new WidthCache(); + map.put(fm, entry); + } else { + entry.up(); + } + return entry; + } + + static synchronized void disposeBy(FontMetrics fm) { + WidthCache entry = (WidthCache) map.get(fm); + if (entry != null) + entry.down(); + } + + private static AbstractMap map = new HashMap(); + } + + public MyFontMetrics(Component component, Font font) { - FontMetrics fm = component.getFontMetrics(font); + fm = component.getFontMetrics(font); width = fm.charWidth('a'); height = fm.getHeight(); ascent = fm.getAscent(); @@ -44,9 +141,69 @@ height -= leading; leading = 0; + + cwidth_cache = CacheFactory.cacheForFontMetrics(fm); + } + + public void finalize() { + CacheFactory.disposeBy(fm); } + + public int width; public int height; public int ascent; public int leading; + public FontMetrics fm; + + private WidthCache cwidth_cache; + + public boolean isMultiCell() { + return cwidth_cache.isMultiCell(); + } + + /* + * Called 'wcwidth' for historical reasons. (see wcwidth(3) on unix.) + * Return how many cells this character occupies. + */ + + public int wcwidth(char c) { + int cell_width = cwidth_cache.cache[c]; // how many cells wide + + if (cell_width == 0) { + // width not cached yet so figure it out + int pixel_width = fm.charWidth(c); + + if (pixel_width == width) { + cell_width = 1; + + } else if (pixel_width == 0) { + cell_width = 1; + + } else { + // round up pixel width to multiple of cell size + // then distill into a width in terms of cells. + int mod = pixel_width % width; + int rounded_width = pixel_width; + if (mod != 0) + rounded_width = pixel_width + (width - mod); + cell_width = rounded_width/width; + if (cell_width == 0) + cell_width = 1; + + cwidth_cache.setMultiCell(true); + } + + cwidth_cache.cache[c] = (byte) cell_width; + } + return cell_width; + } + + /* + * Shift to the multi-cell character regime as soon as we spot one. + * The actual work is done in wcwidth() itself. + */ + void checkForMultiCell(char c) { + wcwidth(c); + } } Index: ReleaseNotes.ivan.txt =================================================================== RCS file: /cvs/core/libsrc/org/netbeans/lib/terminalemulator/ReleaseNotes.ivan.txt,v retrieving revision 1.2.6.1 diff -u -r1.2.6.1 ReleaseNotes.ivan.txt --- ReleaseNotes.ivan.txt 11 Dec 2001 02:06:33 -0000 1.2.6.1 +++ ReleaseNotes.ivan.txt 9 Apr 2002 01:03:45 -0000 @@ -15,6 +15,219 @@ ================================================================================ back to main 3.3. trunk +======= +tag: ivan_13 + +- performance - reduce Interp footprint + This was based on an observation made by Tor that each Interp + ends up creating redundant copies of it's tste tables. + + All Interps now have a static inner class InterpType which + owns the state transition tables and defines the actions.. + Multiple instances of Interps of the same type share InterpTypes. + + Since the state transition actions are now implemented in the + InterpType, they need to receive an instance of an Interp whose state + they will be modifying. This is passed as an AbstractInterp. + Occasionally the passed-in interp has to be cast to the appropriate + subclass of AbstractInterp. + + In order to reduce the number of these casts moved number parsing mgmt + from InterpANSI to AbstractInterp. + + Some Interp subclasses achieved their means by modifying their + state vectors! Since the vectors are now shared that won't do, so a + more appropriate state stack was introduced into InterpDumb. + The stack is cleared in reset(). + + Files: + AbstractInterp.java + InterpANSI.java + InterpDtTerm.java + InterpDumb.java + + +- performance - user cheaper Interps by default. + Jesse (I think) pointed out that NB in general has no use for + ANSI emulation, and that Term should by default use a "dumb" terminal. + This should reduce the # of classes that get loaded in. + This happens in the initialization of 'private Term.interp'. + +- I18N + This addresses the following issues: + + 15333 Cursor isn't on end of text after using CTRL+C [V, ...] + 19570 I18N - The characters of the error message are overlaped. + + Basically Term can now properly handle non-latin characters, like + Japanese. These issues were realy only the tip of the iceberg. Term + did not really work with japanese until now. + The following work: + - Proper cursor position. + - Proper handling of line wrapping and backspacing over it + (for when horizontallyScrollable is false). This is important + for the proper working of Solaris 'vi' in the ja locale. + - Sane reaction to ANSI terminal control escapes. + - Selection works. + - Active regions work. + - Backspace, TAB etc. work. + + There are two big parts to this + + - Rendering characters in grid/cellular fashion. + + The book + Creating Worldwide software (second edition) (Prentice Hall) + Tuthill & Smallberg + discusses (on p98) how some characters might be double width and + presents 'wcswidth(3)' and 'wcwidth(3)' to return the _display_ + width of a given character. The underlying assumption here is that + fixed width fonts are actually quantized width fonts. + + This doesn't seem to be the case for Java fonts. For example the + default ja font I get has 7pixel wide latin characters and + 12 pixel wide japanese characters. What to do? + + Write our own 'wcwidth' that uses Font.charWidth() and rounds it up + to a multiple of the width of the latin char sub-set. + + But that's not enough. Graphics.drawString() will still advance + each glyph by the original width of the font, not our rounded-up + value. One solution is then to use a drawString() per character. + The adopted solution is instead to use GlyphVector's and + Graphics2d.drawGlyphVector() as used in Term.myDrawChars() and + Term.massage_glyphs(). Despite the hairiness of the code there + it turns out to be faster than a drawString() per char by + a good margin. + + - Accounting for the difference in Buffer vs Screen coordinates. + (A reading of the main javadoc comment for class Term would + help understand the rest of this). + + So now we have a situation where a Line holds characters whose + positions are not neccessarily in a 1-1 correspondence with their + cell positions. Mappings are provided in both directions via + Line.bufToCell() and Line.cellToBuf(). They are used in the existing + buffer to view coordinate xform functions (which for example map + a screen position to a character for the purpose of selection). + A variety of other locations had to be adjusted to use these for + proper operation. The driving algorithm for choosing what needs + attention was occurances of st.cursor.col since the cursor + is in cell coordinates. + + These function aren't "cheap" because they count from the beginning + of the line. Cacheing the values is impractical for the following + reasons: + - You need to cache each mapping since they are used with equal + frequency. + - Because Term allows horizontal scrolling a line can + potentially be very long. The index into a line therefore + will range from 0 to Integer.MAX_VALUE. This means + a cache of short's won't do. + - So now we're talking a fair amount of memory that is + probably not justifiable unless we come up with a way to + quickly dispose of the caches. Cache invalidation is + always a tricky problem, but I found out something else. + For a while I installed a wcwidth() cache per line (In + retrospect having wcwidth() manage the cache of course + made a lot more sense) but along the way I discovered + that the per-line cache gets invalidated quite often. + In effect the cache wouldn't have been helpful. + There are some pattern that could use improvement, a bufToCell + immediately followed by a cellToBuf, or a bufToCell(x) followed + by bufToCell(x+n). These could be collapsed into specialized + functions. + + Some neccesssary fallout from all of this ... + + - MyFontMetrics.wcwidth() is expensive given the number of times it + gets called so the resultant width is cached. The cache is indexed + by the char so is naturally Character.MAX_VALUE big. Not a big + chunk of memory in the big scheme of things but it can add up if you + have many Term instances. So there's a pool of them indexed by + FontMetrics. It's unfortunately trickier than you'd think. + See the opening comment in class MyFontMetrics for more info. + + - LineDiscipline had to be adjusted to do something reasonable + with backspaces in line buffered mode. There's a big comment in + LineDiscipline.sendChar(). + To do this it reuires to have a back-pointer to the Term so + we now have StreamTerm.setTerm() which is used in + Term.pushStream(). + + - Dealing with double-width is expensive so we don't want to + compromise speed in 8-bit locales. + + One way to deal with this is to query the "file.encoding" + property but I found that it's value is very variable from + Java release to Java release and probably from platform to + platform. In 1.4 class Charset is supposed to deal with this + but we're not ready for that switch yet. + + What I opted for is having MyFontMetrics.wcwidth check for + variation from an initial width and set a flag (multiCell) + on the first deviation. Various parts of the code then check + MyFontMetrics.isMultiCell(). So, for example, the painting code + now instead of calling Graphics.drawChars() will call + Term.myDrawChars() which will based on this flag do the expensive + or the cheap thing. + + A note on ANSI emulation vs double-width characters. + The ANSI standard doesn't talk abut double-width characters! So + dealing with them is AFAIK up to individual vendors. I've + followed Solaris'es DtTerm behaviour and spent a fair amount of + time making sure that Solaris vi (which can excellently edit + wide-char files under DtTerm) works under Term. + +- bug: Junk characters inserted on character insert. + Occasionally when charactes are shuffled in the Buffer, usually + under vi, junk (usually 0) characters gets inserted into the + Buffer instead of ' ' (ASCII SP)'s. These show up as "squares". + Fixed in Line.insertCharAt(), Line.setCharAt() and + Line.clearToEndFrom() + + Prior to JDK 1.4 ascii 0 was rendered by Swing as a blank. + But under 1.4 this problem is a whole lot more visible. + +- bug: Pathologically slow horizontal scrolling on long lines + Was attempting to render all characters even those that are + not in view. Fixed in Term.paint_line_new(). + +- bug: Cursor gets drawn on the glyph gutter on horizontal scroll. + Fixed by adding an additional check in paint_cursor(); + +- deprecated: Term.goTo(Coord) + Use Term.setCursorCoord(Coord) which matches getCursorCoord(). + +- Misc: + public->private + Buffer.visible_cols + + Buffer.visibleCols() + + StreamTerm.BUFSZ (instead of hard-coded 1024) + + collection of statistics on linefeeds + - Term.paint_line_old() // dead code + + Term.charWidth(char) // See section above on I18N. + + + +================================================================================ +tag: release33 + +- accessibility + Term now implements Accessible and returns an accessible context. + The "accessible description" is set. + The "accessible name" is set from OW to be the same as the tab name. + +- performance + - Added Term.setKeyStrokeSet() in order to allow sharing of, + sometimes large, sets. + Added code to OW to take advantage of this. + See 'updateKeyStrokeSet()' and 'getCommonKeyStrokeSet()'. + This code also tracks changes to the global keymap so that Term will + now pass through newly added keyboard accelerators. + +================================================================================ +back to main 3.3. trunk - bug: Missing ANSI escape sequence handling: - ESC [ 4 h set insert/overstrike mode Index: Screen.java =================================================================== RCS file: /cvs/core/libsrc/org/netbeans/lib/terminalemulator/Screen.java,v retrieving revision 1.2 diff -u -r1.2 Screen.java --- Screen.java 24 Aug 2001 13:48:25 -0000 1.2 +++ Screen.java 9 Apr 2002 01:03:46 -0000 @@ -44,6 +44,7 @@ Dimension dim = new Dimension(dx, dy); setSize(dim); setPreferredSize(dim); + // setOpaque(true); // see comment in Term.repaint() if (debug) { // Just turning our double buffering isn't enough, need to @@ -58,9 +59,15 @@ } + /* OLD deprecated + */ public boolean isManagingFocus() { // So we see TABs // Except it doesn't work as advertised + // Actually, if I don't do this I get no notification of TAB keys, + // so leave it in. + // This function is deprecated under 1.4. Need to use + // Component.setFocusTraversalKeysEnabled() instead. return true; } Index: Sel.java =================================================================== RCS file: /cvs/core/libsrc/org/netbeans/lib/terminalemulator/Sel.java,v retrieving revision 1.5 diff -u -r1.5 Sel.java --- Sel.java 4 Oct 2001 09:59:59 -0000 1.5 +++ Sel.java 9 Apr 2002 01:03:46 -0000 @@ -105,7 +105,7 @@ * Adjust the selection against 'bias'. *

* As the selection reaches the top of the history buffer it will get - * trimmed until eventually allof it will go away. + * trimmed until eventually all of it will go away. * * This form doesn't work if the selection is "split" by insertion of * lines. Maybe we SHOULD have two arguments, adjust origin and adjust @@ -260,7 +260,9 @@ * The string created in sel_done should be retained until * this function called. */ - // System.out.println("lostOwnership()"); // NOI18N + /* DEBUG + System.out.println("lostOwnership()"); // NOI18N + */ if (cancel(true)) term.repaint(false); } @@ -288,13 +290,13 @@ * added, removed or cleared. */ int intersection(int line) { - /* + /* DEBUG if (sel_origin == null) { - System.out.println("Sel.intersection(" + line + ") no selection"); + System.out.println("Sel.intersection(" + line + ") no selection"); // NOI18N } else { - System.out.println("Sel.intersection(" + line + ")" + - " sel_origin.row = " + sel_origin.row + - " sel_extent.row = " + sel_extent.row); + System.out.println("Sel.intersection(" + line + ")" + // NOI18N + " sel_origin.row = " + sel_origin.row + // NOI18N + " sel_extent.row = " + sel_extent.row); // NOI18N } */ @@ -331,10 +333,14 @@ begin = term.toViewCoord(begin); end = term.toViewCoord(end); + int lw; // width of last character in selection + Line l = term.buf.lineAt(row); + lw = l.width(term.metrics, ecol); + Point pbegin = term.toPixel(begin); Point pend = term.toPixel(end); pend.y += term.metrics.height; - pend.x += term.metrics.width; // xterm actually doesn't do this + pend.x += term.metrics.width * lw; // xterm actually doesn't do this Dimension dim = new Dimension(pend.x - pbegin.x, pend.y - pbegin.y); Index: State.java =================================================================== RCS file: /cvs/core/libsrc/org/netbeans/lib/terminalemulator/State.java,v retrieving revision 1.5 diff -u -r1.5 State.java --- State.java 6 Nov 2001 02:14:41 -0000 1.5 +++ State.java 9 Apr 2002 01:03:46 -0000 @@ -28,6 +28,7 @@ public int firstx; public int firsty; + // Cursor is in "cell" coordinates public BCoord cursor = new BCoord(); public void adjust(int amount) { Index: StreamTerm.java =================================================================== RCS file: /cvs/core/libsrc/org/netbeans/lib/terminalemulator/StreamTerm.java,v retrieving revision 1.5 diff -u -r1.5 StreamTerm.java --- StreamTerm.java 4 Oct 2001 09:59:59 -0000 1.5 +++ StreamTerm.java 9 Apr 2002 01:03:46 -0000 @@ -111,7 +111,8 @@ * Monitor output from process and forward to terminal */ private class OutputMonitor extends Thread { - private char[] buf = new char[1024]; + private static final int BUFSZ = 1024; + private char[] buf = new char[BUFSZ]; private Term term; private InputStreamReader reader; @@ -163,14 +164,14 @@ try { while(true) { - int nread = reader.read(buf, 0, 1024); + int nread = reader.read(buf, 0, BUFSZ); if (nread == -1) { // This happens if someone closes the input stream, // say the master end of the pty. /* When we clean up this gets closed so it's not always an error. - System.err.println("com.sun.spro.Term.OutputMonitor: " + - "Input stream closed");inp + System.err.println("com.sun.spro.Term.OutputMonitor: " + // NOI18N + "Input stream closed");inp // NOI18N */ break; } Index: Term.java =================================================================== RCS file: /cvs/core/libsrc/org/netbeans/lib/terminalemulator/Term.java,v retrieving revision 1.13.2.3.4.1 diff -u -r1.13.2.3.4.1 Term.java --- Term.java 27 Mar 2002 14:17:49 -0000 1.13.2.3.4.1 +++ Term.java 9 Apr 2002 01:03:53 -0000 @@ -29,6 +29,9 @@ import javax.swing.*; import javax.accessibility.*; +import java.awt.font.*; +import java.awt.geom.Point2D; + import java.util.HashSet; import java.util.Date; import java.util.LinkedList; @@ -48,36 +51,93 @@

  • History buffer.
  • Support for nested pickable regions in order to support hyperlinked views or more complex active text utilities. +
  • Support for double-width Oriental characters.

    Coordinate systems

    -Two coordinate systems are used with Term. They are both cartesian and -have their origin at the top left. But they differ in all other respects: +The following coordinate systems are used with Term. +They are all cartesian and have their origin at the top left. +All but the first are 0-origin. +But they differ in all other respects:
    -
    Screen coordinates + +
    ANSI Screen coordinates
    Address only the visible portion of the screen. They are 1-origin and extend thru the width and height of the visible portion of the screen per getColumns() and getRows().

    - This coordinate system is used mostly by the screen Interp classes and - Ops. + This is how an application (like 'vi' etc) views the screen. + This coordinate system primarily comes into play in the cursor addressing + directive, op_cm() and otherwise is not really used in the implementation. +

    + +

    Cell coordinates +
    + Each character usually takes one cell, and all placement on the screen + is in terms of a grid of cells getColumns() wide This cellular nature + is why fixed font is "required". In some locales some characters may + be double-width. + Japanese characters are like this, so they take up two cells. + There are no double-height characters (that I know of). +

    + Cursor motion is in cell coordinates, so to move past a Japanese character + you need the cursor to move right twice. A cursor can also be placed on + the second cell of a double-width character. +

    + Note that this is strictly an internal coordinate system. For example + Term.getCursorCol() and getCursorCoord() return buffer coordinates. +

    + The main purpose of this coordinate system is to capture columns. + In the vertical direction sometimes it extends only the height of the + screen and sometimes the height of the buffer.

    -

    Buffer coordinates + +
    Buffer coordinates ...
    - Address the whole history buffer. + ... address the whole history character buffer. These are 0-origin and extend thru the width - of the screen per getColumns() and the whole history (for which - there is no property yet. getHistorySize() isn't it). + of the screen per getColumns(), or more if horizontal scrolling is + enabled, and the whole history (that's getHistorySize()+getRows()).

    - The Coord class captures the value of such coordinates. + The BCoord class captures the value of such coordinates. It is more akin to the 'int offset' used in the Java text package as opposed to javax.swing.text.Position. +

    + If there are no double-width characters the buffer coords pretty much + overlap with cell coords. If double-width characters are added then + the buffer column and cell column will have a larger skew the more right + you go. +

    +

    Absolute coordinates ... +
    + ... are like Buffer coordinates in the horizontal direction. + In the vertical direction their origin is the first line that was + sent to the terminal. This line might have scrolled out of history and + might no longer be in the buffer. In effect each line ever printed by + Term gets a unique Absolute row.

    - Most, but not all, of the methods against Term and friends use the - buffer coordinate system, but it hasn't all yet been regularized - so keep an eye out for unexpected use of screen coordinates. - + What good is this? The ActiveRegion mechanism maintains coordinates + for its' boundaries. As text scrolls out of history buffer row coordinates + have to shift and all ActiveRegions' coords need to be relocated. This + can get expensive because as soon as the history buffer becomes full + each newline will require a relocation. This is the approach that + javax.swing.text.Position implements and it's justified there because + no Swing component has a "history buffer". + However, if you use absolute coordinates you'll never have to + relocate anything! Simple and effective. +

    + Well almost. What happens when you reach Integer.MAX_VALUE? You wrap and + that can confuse everything. What are the chances of this happening? + Suppose term can process 4000 lines per second. A runaway process will + produce Integer.MAX_VALUE lines in about 4 days. That's too close + for comfort, so Term does detect the wrap and only then goes and + relocates stuff. This, however, causes a secondary problem with + testability since no-one wants to wait 4 days for a single wrap. + So what I've done is periodically set Term.modulo to something + smaller and tested stuff. +

    + I'm indebted to Alan Kostinsky for this bit of lateral thinking.

    @@ -110,9 +170,9 @@ This is the primary facility that XTerm and other derivatives provide. The screen has a history buffer in the vertical dimension.

    - Buffer coordinates play a role in this mode and it it crucial to - remember that as the contents of the buffer scrolls and old - lines wink out of history coordinates become invalidated. + Because of limited history active regions can scroll out of history and + while the coordinate invalidation problem is not addressed by absolute + coordiantes sometimes we don't want stuff to wink out.
    Which is why we have ... @@ -120,10 +180,9 @@

    Page mode
    It is possible to "anchor" a location in the buffer and prevent it - from going out of history. While this can be helpful in having the + from going out of history. This can be helpful in having the client of Term make sure that crucial output doesn't get lost due to - short-sighted history settings on the part of the user, the main reason - is to allow for persistence of coordinate settings. + short-sighted history settings on the part of the user.

    To use Term in this mode you can use setText() or appendText() instead of @@ -146,9 +205,9 @@ Term is not a document editing widget.

  • -Term is also not a command line processor in the sense that a Windows +Term is also not a command line processor in the sense that a MS Windows console is. Its shuttling of keyboard events to an output stream and -rendering of characters on the input stream unto the screen are completely +rendering of characters from the input stream unto the screen are completely independent activities.

    This is due to Terms unix heritage where shells (ksh, bash etc) do their own @@ -183,6 +242,7 @@ // statistics private int n_putchar; private int n_putchars; + private int n_linefeeds; private int n_repaint; private int n_paint; @@ -352,6 +412,8 @@ stream.setToDTE(dce_end); dce_end = stream; } + + stream.setTerm(this); } /* @@ -587,7 +649,7 @@ public void clearHistoryNoRefresh() { sel.cancel(true); - int old_cols = buf.visible_cols; + int old_cols = buf.visibleCols(); buf = new Buffer(old_cols); st.firstx = 0; @@ -943,6 +1005,7 @@ // vertical (row) dimension if (st.cursor.row >= st.firstx && st.cursor.row < st.firstx + st.rows) { + ; } else { st.firstx = buf.nlines - st.rows; repaint(true); @@ -998,18 +1061,25 @@ } } - // System.out.println("Checking hscroll cursor.col " + st.cursor.col + - // " firsty " + st.firsty + " visible_cols " + buf.visible_cols); + /* DEBUG + System.out.println("Checking hscroll cursor.col " + st.cursor.col + // NOI18N + " firsty " + st.firsty // NOI18N + " visibleCols " + buf.visibleCols()); // NOI18N + */ // horizontal (col) dimension - if (st.cursor.col >= st.firsty + buf.visible_cols) { - // System.out.println("Need to scroll right"); - st.firsty = st.cursor.col - buf.visible_cols + 1; + if (st.cursor.col >= st.firsty + buf.visibleCols()) { + /* DEBUG + System.out.println("Need to scroll right"); // NOI18N + */ + st.firsty = st.cursor.col - buf.visibleCols() + 1; repaint(true); - } else if (st.cursor.col - buf.visible_cols < st.firsty) { - // System.out.println("Need to scroll left"); - st.firsty = st.cursor.col - buf.visible_cols + 1; + } else if (st.cursor.col - buf.visibleCols() < st.firsty) { + /* DEBUG + System.out.println("Need to scroll left"); // NOI18N + */ + st.firsty = st.cursor.col - buf.visibleCols() + 1; if (st.firsty < 0) st.firsty = 0; else @@ -1097,7 +1167,6 @@ // Really weird I seem to get the same results regardless of // whether I use orows or buf.nlines. SHOULD investigate more. - // int allowed = orows - st.cursor.row - 1; int allowed = buf.nlines - st.cursor.row - 1; if (allowed < 0) @@ -1173,7 +1242,9 @@ // cull any regions that are no longer in history if (++cull_count % cull_frequency == 0) { - // System.out.println("Culling regions ..."); // NOI18N + /* DEBUG + System.out.println("Culling regions ..."); // NOI18N + */ region_manager.cull(firsta); } @@ -1244,12 +1315,12 @@ c = 0; } else if ((direction & RIGHT) == RIGHT) { st.firsty ++; - int limit = buf.totalCols() - buf.visible_cols; + int limit = buf.totalCols() - buf.visibleCols(); if (limit < 0) limit = 0; if (st.firsty > limit) st.firsty = limit; - c = st.firsty + buf.visible_cols; + c = st.firsty + buf.visibleCols(); } BCoord vc = new BCoord(r, c); @@ -1263,16 +1334,18 @@ public void run() { while (true) { - // System.out.print("Scrolling "); - // if ((direction & UP) == UP) - // System.out.print("UP "); - // if ((direction & DOWN) == DOWN) - // System.out.print("DOWN "); - // if ((direction & LEFT) == LEFT) - // System.out.print("LEFT "); - // if ((direction & RIGHT) == RIGHT) - // System.out.print("RIGHT "); - // System.out.println(); + /* DEBUG + System.out.print("Scrolling "); // NOI18N + if ((direction & UP) == UP) + System.out.print("UP "); // NOI18N + if ((direction & DOWN) == DOWN) + System.out.print("DOWN "); // NOI18N + if ((direction & LEFT) == LEFT) + System.out.print("LEFT "); // NOI18N + if ((direction & RIGHT) == RIGHT) + System.out.print("RIGHT "); // NOI18N + System.out.println(); + */ extend(); @@ -1282,7 +1355,9 @@ break; } } - // System.out.println("Done with Scrolling"); + /* DEBUG + System.out.println("Done with Scrolling"); // NOI18N + */ } } @@ -1354,7 +1429,7 @@ BorderLayout layout = new BorderLayout(); setLayout(layout); screen = new Screen(this, - (buf.visible_cols * metrics.width + + (buf.visibleCols() * metrics.width + glyph_gutter_width + debug_gutter_width), st.rows * metrics.height); @@ -1389,11 +1464,19 @@ // deal with the user moving the thumb st.firstx = pos; + /* DEBUG + if (st.firstx + st.rows > buf.nlines) { + Thread.dumpStack(); + printStats("bad scroll value"); // NOI18N + } + */ repaint(false); break; default: - // System.out.println("adjustmentValueChanged: " + e); // NOI18N + /* DEBUG + System.out.println("adjustmentValueChanged: " + e); // NOI18N + */ break; } } @@ -1429,7 +1512,9 @@ break; default: - // System.out.println("adjustmentValueChanged: " + e); // NOI18N + /* DEBUG + System.out.println("adjustmentValueChanged: " + e); // NOI18N + */ break; } } @@ -1483,7 +1568,9 @@ } public void keyPressed(KeyEvent e) { - // System.out.println("keyPressed " + e); // NOI18N + /* DEBUG + System.out.println("keyPressed " + e); // NOI18N + */ switch (e.getKeyCode()) { case KeyEvent.VK_COPY: @@ -1508,9 +1595,14 @@ } public void keyReleased(KeyEvent e) { - // System.out.println("keyReleased"); // NOI18N + /* DEBUG + System.out.println("keyReleased"); // NOI18N + */ + if (e.getKeyCode() == KeyEvent.VK_ENTER) { - // System.out.println("keyReleased VK_ENTER"); // NOI18N + /* DEBUG + System.out.println("keyReleased VK_ENTER"); // NOI18N + */ saw_return = false; } maybeConsume(e); @@ -1520,14 +1612,18 @@ screen.addMouseMotionListener(new MouseMotionListener() { public void mouseDragged(MouseEvent e) { - // System.out.println("mouseDragged"); // NOI18N + /* DEBUG + System.out.println("mouseDragged"); // NOI18N + */ if (left_down_point != null) { BCoord bc = toBufCoords(toViewCoord(left_down_point)); sel.track(new Coord(bc, firsta)); left_down_point = null; } drag_point = e.getPoint(); - // System.out.println("mouseDrag: " + drag_point); // NOI18N + /* DEBUG + System.out.println("mouseDrag: " + drag_point); // NOI18N + */ int scroll_direction = 0; @@ -1547,15 +1643,17 @@ } public void mouseMoved(MouseEvent e) { - /* + /* DEBUG Point p = (Point) e.getPoint().clone(); BCoord bc = toBufCoords(toViewCoord(p)); Coord c = new Coord(bc, firsta); Extent x = sel.getExtent(); if (x == null) { - // System.out.println("sel intersect: no extent"); + System.out.println("sel intersect: no extent"); // NOI18N } else { - // System.out.println("sel intersect: " + (x.intersects(c.row, c.col)? "intersects": "doesn't intersect")); + System.out.println("sel intersect: " + // NOI18N + (x.intersects(c.row, c.col)? "intersects" // NOI18N + "doesn't intersect")); // NOI18N } */ } @@ -1564,7 +1662,9 @@ screen.addMouseListener(new MouseListener() { public void mouseClicked(MouseEvent e) { - // System.out.println("mouseClicked"); // NOI18N + /* DEBUG + System.out.println("mouseClicked"); // NOI18N + */ BCoord bcoord = toBufCoords(toViewCoord(e.getPoint())); if (SwingUtilities.isLeftMouseButton(e)) { @@ -1573,8 +1673,10 @@ screen.requestFocus(); } else if ( SwingUtilities.isMiddleMouseButton(e)) { - // System.out.println("MIDDLE click"); // NOI18N - // System.out.println("Selection: '" + sel.sel_get() + "'"); // NOI18N + /* DEBUG + System.out.println("MIDDLE click"); // NOI18N + System.out.println("Selection: '" + sel.sel_get() + "'"); // NOI18N + */ paste(); } else if (SwingUtilities.isRightMouseButton(e)) { @@ -1703,8 +1805,8 @@ if (message != null) System.out.print("\t"); // NOI18N - System.out.println("rows " + st.rows + - " v cols " + buf.visible_cols + // NOI18N + System.out.println("rows " + st.rows + // NOI18N + " v cols " + buf.visibleCols() + // NOI18N " t cols " + buf.totalCols() + // NOI18N " history " + history_size + // NOI18N " firstx " + st.firstx + // NOI18N @@ -1722,6 +1824,7 @@ System.out.print("\t"); // NOI18N System.out.println("putChar " + n_putchar + // NOI18N " putChars " + n_putchars + // NOI18N + " linefeeds " + n_linefeeds + // NOI18N " repaint " + n_repaint + // NOI18N " paint " + n_paint); // NOI18N } @@ -1813,10 +1916,25 @@ } /** + * Trampoline from Line.ensureCapacity() to Buffer.noteColumn() + */ + void noteColumn(Line l, int capacity) { + int vcapacity = l.bufToCell(metrics, capacity); + buf.noteColumn(vcapacity); + } + + /** + * Trampoline from Line to MyFontMetrics.checkForMultiCell() + */ + void checkForMultiCell(char c) { + metrics.checkForMultiCell(c); + } + + /** * Get the number of character columns in the screen */ public int getColumns() { - return buf.visible_cols; + return buf.visibleCols(); } /** @@ -1892,7 +2010,7 @@ */ public void fillSizeInfo(Dimension cells, Dimension pixels) { cells.height = st.rows; - cells.width = buf.visible_cols; + cells.width = buf.visibleCols(); Dimension cpixels = screen.getSize(); pixels.width = cpixels.width - glyph_gutter_width - debug_gutter_width; pixels.height = cpixels.height; @@ -1904,7 +2022,7 @@ */ protected void updateTtySize() { if (screen != null) { - Dimension cells = new Dimension(buf.visible_cols, st.rows); + Dimension cells = new Dimension(buf.visibleCols(), st.rows); Dimension pixels = screen.getSize(); fireSizeChanged(cells, pixels); } @@ -1918,7 +2036,8 @@ /* * Convert from buffer coords to view coords */ - BCoord v = new BCoord(b.row - st.firstx, b.col - st.firsty); + int vc = buf.lineAt(b.row).bufToCell(metrics, b.col); + BCoord v = new BCoord(b.row - st.firstx, vc - st.firsty); return v; } @@ -1949,7 +2068,7 @@ private BCoord toViewCoord(Point p) { BCoord v = new BCoord(p.y / metrics.height, (p.x - glyph_gutter_width - debug_gutter_width) / metrics.width); - v.clip(st.rows, buf.visible_cols); + v.clip(st.rows, buf.visibleCols()); // System.out.println("toViewCoord() -> " + v); // NOI18N return v; } @@ -1962,8 +2081,9 @@ int brow = st.firstx + v.row; if (brow >= buf.nlines) brow = buf.nlines-1; - BCoord b = new BCoord(brow, st.firsty + v.col); - // System.out.println("toBufCoords() -> " + b); // NOI18N + int bc = buf.lineAt(brow).cellToBuf(metrics, st.firsty + v.col); + BCoord b = new BCoord(brow, bc); + // System.out.println("toBufCoords(" + v + ") -> " + b); // NOI18N return b; } @@ -2013,24 +2133,47 @@ } } + private Color actual_foreground; private Color actual_background; private boolean check_selection; private int totcols; private void do_run(Graphics g, int yoff, int xoff, int baseline, - int brow, char buf[], + int brow, char buf[], Line l, int attr, int rbegin, int rend) { - // System.out.println("do_run(" + rbegin + ", " + rend + ")"); + // System.out.println("do_run(" + rbegin + ", " + rend + ")"); // NOI18N - int x = xoff + (rbegin - st.firsty) * metrics.width; - int rlength = rend - rbegin + 1; - if (rlength <= 0) { - System.out.println("do_run(" + rbegin + ", " + rend + ")"); - return; + int x; + int rlength; + int xlength; + + if (metrics.isMultiCell()) { + int vbegin = l.bufToCell(metrics, rbegin); + int vend = l.bufToCell(metrics, rend+1)-1; + x = xoff + (vbegin - st.firsty) * metrics.width; + int vlength = vend - vbegin + 1; + if (vlength <= 0) { + /* DEBUG + System.out.println("do_run(" + rbegin + ", " + rend + ")"); // NOI18N + */ + return; + } + rlength = rend - rbegin + 1; + xlength = vlength * metrics.width; + + } else { + x = xoff + (rbegin - st.firsty) * metrics.width; + rlength = rend - rbegin + 1; + if (rlength <= 0) { + /* DEBUG + System.out.println("do_run(" + rbegin + ", " + rend + ")"); // NOI18N + */ + return; + } + xlength = rlength * metrics.width; } - int xlength = rlength * metrics.width; boolean reverse = ((attr & Attr.REVERSE) == Attr.REVERSE); boolean active = ((attr & Attr.ACTIVE) == Attr.ACTIVE); @@ -2097,30 +2240,177 @@ } // Draw the foreground character glyphs - g.drawChars(buf, rbegin, rlength, x, baseline); + myDrawChars(g, buf, l, rbegin, rlength, x, baseline); // Draw fake bold characters by redrawing one pixel to the right if ( (attr & Attr.BRIGHT) == Attr.BRIGHT) { - g.drawChars(buf, rbegin, rlength, x+1, baseline); + myDrawChars(g, buf, l, rbegin, rlength, x+1, baseline); + } + } + + + + private final Point newp = new Point(); + + /* + * Tweak glyph X positions so they fall on cell/grid/column boundries. + */ + private void massage_glyphs(GlyphVector gv, int start, int n, Line l) { + Point2D pos0 = gv.getGlyphPosition(0); + + // There's one big assumption here that in a monospaced font all the + // Y placements are identical. So we use the placement for the first + // glyph only. + newp.y = (int) pos0.getY(); + + int col = (int) pos0.getX(); + for (int gx = 0; gx < n; gx++) { + newp.x = col; + gv.setGlyphPosition(gx, newp); + col += l.width(metrics, start + gx) * metrics.width; + } + } + + /** + * Draw characters in cells. + * + * Fixed width or monospaced fonts implies that the glyphs of all characters + * have the same width. Some non-latin characters (japanese) might have + * glyph widths that are an _integer multiple_ of the latin glyphs. Thus + * cellular (grid based) text widget like this termulator can still place + * all characters nicely. There is a 'C' function wcwidth() which + * ... determines the number of _column_ positions ... and CDE's DtTrem + * ultimately depends on it to place things. (See also Tuthill & Smallberg, + * "Creating worldwide software" PrenticeHall 2nd ed. p98) + * + * Unfortunately the fonts used by Java, even the "monospaced" fonts, do + * not abide by the above convention. I measured a 10pt ja locale latin + * character at 7 pixels wide and a japanese character at 12 pixels wide, + * instead of 14. A similar problem existed with respect to the "unprintbale" + * placeholder square. Until Java 1.4 it used to be 9 or 10 pixels wide! + * The square is fixed, but I"m not sure the above will be anytime soon. + * + * What this means is that Graphics.drawString() when given a mix and match + * of latin and japanese characters will not place them right. Selection + * doesn't work etc. + * + * Nor does Java provide anything resembling wcwidth() so we're rolling + * our own here. That's done in Line.width(). + * + * So one approach would be to place each character individually, but it's + * rather slow. Fortunately Java provides a GlyphVector class that allows + * us to tweak the positions of the glyphs. The timing I"ve gotten are + * 50 for one drawChars() per charactr. (SLOWER below) + * 15 using the GlyphVector technique + * 8 using plain drawChars + * Unfortunately GlyphVector's interface leaves a bit to be desired. + * - It does not take a (char [], offset, length) triple and depends + * on the length of the char array passed in. Since our Line char arrays + * have some slop in them we can't pass them directly. Hence the + * "new char[]" and the "System.arraycopy". + * - The interface for getting and setting positions is also a bit + * awkward as you may notice from massage_glyphs(). + * + * We SHOULD fall back on plain drawChars() if the host charset is an + * 8 bit encoding like ASCII or ISO 8859. This encoding is available + * via System.getProperty("file.encoding") but there are so many aliases + * for each that I"m wary of hardcoding tests. See + * http://www.iana.org/assignments/character-sets + * Java 1.4 has class Charset that helps with the aliases but we can't + * yet lock into 1.4. + */ + + private void myDrawChars(Graphics g, char buf[], Line l, + int start, int howmany, int xoff, int baseline) { + if (metrics.isMultiCell()) { + // slow way + // This looks expensive but it is in fact a whole lot faster + // than issuing a g.drawChars() _per_ character + + Graphics2D g2 = (Graphics2D) g; + FontRenderContext frc = g2.getFontRenderContext(); + // Gaaah, why doesn't createGlyphVector() take a (char[],offset,len) + // triple? + char[] tmp = new char[howmany]; + System.arraycopy(buf, start, tmp, 0, howmany); + GlyphVector gv = getFont().createGlyphVector(frc, tmp); + massage_glyphs(gv, start, howmany, l); + g2.drawGlyphVector(gv, xoff, baseline); + } else { + // fast way + g.drawChars(buf, start, howmany, xoff, baseline); } } /* * Render one line + * Draw the line on this brow (buffer row 0-origin) */ private void paint_line_new(Graphics g, Line l, int brow, int xoff, int yoff, int baseline, Extent selx) { - // draw the line on this brow (buffer row 0-origin) int length = l.length(); if (length == 0) return; - int howmany = length-st.firsty; - if (howmany <= 0) + int lastcol; + int firstcol; + + if (metrics.isMultiCell()) { + + // Figure what buffer column is the first visible one (moral + // equivalent of st.firsty) + + // SHOULD replace with something that does cellToBuf/bufToCell + // all at once. There are a couple of other occurances of this + // pattern. + + firstcol = l.cellToBuf(metrics, st.firsty); + int inverse_firstcol = l.bufToCell(metrics, firstcol); + int delta = st.firsty - inverse_firstcol; + if (delta > 0) { + /* This is what to do if we want to draw the right half of the + * glyph. However the left half of it will end up in the glyph + * gutter and to compensate for thet we'll need to tweak the + * clip region. For now taking the easy way out> + + int pdelta = delta * metrics.width; // pixel delta + xoff -= pdelta; + + */ + + firstcol++; + int pdelta = delta * metrics.width; // pixel delta + xoff += pdelta; + } + + lastcol = l.cellToBuf(metrics, st.firsty + buf.visibleCols() - 1); + + /* DEBUG + System.out.print + ("firstcol = " + firstcol + " for firsty " + st.firsty); // NOI18N + System.out.print + (" delta = " + delta); // NOI18N + System.out.println + (" lastcol = " + lastcol + // NOI18N + " for visibleCols " + buf.visibleCols()); // NOI18N + */ + + } else { + lastcol = st.firsty + buf.visibleCols() - 1; + firstcol = st.firsty; + } + + + lastcol = Math.min(lastcol, length-1); + if (firstcol > lastcol) return; + int howmany = lastcol - firstcol + 1; + + + // 'length' is not used from here on down char buf[] = l.charArray(); @@ -2130,12 +2420,14 @@ if (l.isWrapped() && l.isAboutToWrap()) g.setColor(Color.red); // not a good state to be in else if (l.isAboutToWrap()) - g.setColor(Color.pink); + g.setColor(Color.orange); else if (l.isWrapped()) g.setColor(Color.magenta); } - g.drawChars(buf, st.firsty, howmany, xoff, baseline); + myDrawChars(g, buf, l, firstcol, howmany, xoff, baseline); + + return; } @@ -2166,9 +2458,8 @@ } // iterate through runs - int lastcol = st.firsty + howmany; - int rbegin = st.firsty; + int rbegin = firstcol; int rend = rbegin; while (true) { @@ -2179,7 +2470,7 @@ int attr = attrs[rbegin]; rend = rbegin+1; - while (rend < lastcol) { + while (rend <= lastcol) { if (attrs[rend] != attr) break; rend++; @@ -2195,45 +2486,53 @@ if (sbegin == -1 || send < rbegin || sbegin > rend) { // run is not in selection do_run(g, yoff, xoff, - baseline, brow, buf, attr, rbegin, rend); + baseline, brow, buf, l, attr, rbegin, rend); } else if (sbegin <= rbegin && send >= rend) { // run entirely in selection - // System.out.println("run entirely in selection"); + /* DEBUG + System.out.println("run entirely in selection"); // NOI18N + */ do_run(g, yoff, xoff, - baseline, brow, buf, alt_attr, rbegin, rend); + baseline, brow, buf, l, alt_attr, rbegin, rend); } else if (sbegin > rbegin && send < rend) { // selection fully within run // split into three parts - // System.out.println("run selection fully within run"); + /* DEBUG + System.out.println("run selection fully within run"); // NOI18N + */ do_run(g, yoff, xoff, - baseline, brow, buf, attr, rbegin, sbegin-1); + baseline, brow, buf, l, attr, rbegin, sbegin-1); do_run(g, yoff, xoff, - baseline, brow, buf, alt_attr, sbegin, send); + baseline, brow, buf, l, alt_attr, sbegin, send); do_run(g, yoff, xoff, - baseline, brow, buf, attr, send+1, rend); + baseline, brow, buf, l, attr, send+1, rend); } else if (sbegin <= rbegin) { // selection covers left portion of run - // System.out.println("selection covers left portion of run"); + /* DEBUG + System.out.println("selection covers left portion of run"); // NOI18N + */ // split into two parts do_run(g, yoff, xoff, - baseline, brow, buf, alt_attr, rbegin, send); + baseline, brow, buf, l, alt_attr, rbegin, send); do_run(g, yoff, xoff, - baseline, brow, buf, attr, send+1, rend); + baseline, brow, buf, l, attr, send+1, rend); } else if (send >= rend) { // selection covers right portion of run // split into two parts - // System.out.println("selection covers right portion of run"); + /* DEBUG + System.out.println("selection covers right portion of run"); // NOI18N + */ do_run(g, yoff, xoff, - baseline, brow, buf, attr, rbegin, sbegin-1); + baseline, brow, buf, l, attr, rbegin, sbegin-1); do_run(g, yoff, xoff, - baseline, brow, buf, alt_attr, sbegin, rend); + baseline, brow, buf, l, alt_attr, sbegin, rend); } else { - // System.out.println("Odd run/selection overlap"); + // System.out.println("Odd run/selection overlap"); // NOI18N } if (rend+1 >= lastcol) @@ -2244,138 +2543,6 @@ } } - private void paint_line_old(Graphics g, Line l, int brow, - int xoff, int yoff, int baseline, - Extent selx) { - // draw the line on this brow (buffer row 0-origin) - - int length = l.length(); - if (length == 0) - return; - - int howmany = length-st.firsty; - if (howmany <= 0) - return; - - char buf[] = l.charArray(); - - if (! l.hasAttributes()) { - - if (debugWrap()) { - if (l.isWrapped() && l.isAboutToWrap()) - g.setColor(Color.red); // not a good state to be in - else if (l.isAboutToWrap()) - g.setColor(Color.pink); - else if (l.isWrapped()) - g.setColor(Color.magenta); - } - - g.drawChars(buf, st.firsty, howmany, xoff, baseline); - return; - } - - int attrs[] = l.attrArray(); - - int lastcol = st.firsty + howmany; - int x = xoff; - - for (int col = st.firsty; col < lastcol; col++) { - int attr = attrs[col]; - boolean reverse = ((attr & Attr.REVERSE) == Attr.REVERSE); - boolean active = ((attr & Attr.ACTIVE) == Attr.ACTIVE); - boolean need_rect = reverse || active; - boolean override_fg = false; - - // choose background color - // If we're doing 'reverse' video then we use FG colors for BG. - Color bg; - if (reverse) { - int fcx = Attr.foregroundColor(attr); - if (fcx != 0 && fcx <= 8) { - bg = standard_color[fcx-1]; - need_rect = true; - } else if (fcx != 0 && fcx > 8) { - bg = custom_color[fcx-9]; - need_rect = true; - } else { - bg = actual_foreground; - } - - } else { - int bcx = Attr.backgroundColor(attr); - if (bcx != 0 && bcx <= 8) { - bg = standard_color[bcx-1]; - need_rect = true; - } else if (bcx > 8) { - bg = custom_color[bcx-9]; - need_rect = true; - } else { - bg = actual_background; - } - } - - if (check_selection && - selx.intersects(firsta + brow, col)) { - - need_rect = false; - override_fg = true; - } - - if (need_rect) { - // Draw any background - if (active) { - g.setColor(active_color); - } else { - g.setColor(bg); - } - g.fillRect(x, yoff, - metrics.width, metrics.height - metrics.leading); - } - - // Set foreground color - Color fg; - if (override_fg) { - fg = actual_foreground; - } else if (reverse) { - int bcx = Attr.backgroundColor(attr); - if (bcx != 0 && bcx <= 8) { - fg = standard_color[bcx-1]; - } else if (bcx > 8) { - fg = custom_color[bcx-9]; - } else { - fg = actual_background; - } - - } else { - int fcx = Attr.foregroundColor(attr); - if (fcx != 0 && fcx <= 8) { - fg = standard_color[fcx-1]; - } else if (fcx != 0 && fcx > 8) { - fg = custom_color[fcx-9]; - } else { - fg = actual_foreground; - } - } - g.setColor(fg); - - // draw any underscores - if ( (attr & Attr.UNDERSCORE) == Attr.UNDERSCORE) { - int h = metrics.height - metrics.leading - 1; - g.drawLine(x, yoff+h, x + metrics.width, yoff+h); - } - - // Draw the foreground character glyphs - g.drawChars(buf, col, 1, x, baseline); - - // Draw fake bold characters by redrawing one pixel to the right - if ( (attr & Attr.BRIGHT) == Attr.BRIGHT) { - g.drawChars(buf, col, 1, x+1, baseline); - } - - x += metrics.width; - } - } - synchronized void do_paint(Graphics g) { /* @@ -2391,7 +2558,14 @@ return; } - // long paint_start_time = System.currentTimeMillis(); + /* DEBUG + long paint_start_time = System.currentTimeMillis(); + */ + + // If Screen is opaque it seems that there is a bug in Swing where + // the Graphics that we get here ends up with fonts other than what + // we assigned to Term. So we make doubly sure here. + g.setFont(getFont()); n_paint++; @@ -2456,7 +2630,9 @@ for (int vrow = 0; vrow < st.rows; vrow++) { Line l = buf.lineAt(lx); if (l == null) { + /* DEBUG System.out.println("vrow " + vrow + " lx " + lx); // NOI18N + */ printStats(null); break; } @@ -2498,9 +2674,11 @@ if (debugMargins()) paint_margins(g); - // long paint_stop_time = System.currentTimeMillis(); - // long paint_time = paint_stop_time - paint_start_time; - // System.out.println("paint_time = " + paint_time); + /* DEBUG + long paint_stop_time = System.currentTimeMillis(); + long paint_time = paint_stop_time - paint_start_time; + System.out.println("paint_time = " + paint_time); // NOI18N + */ } private void paint_margins(Graphics g) { @@ -2523,7 +2701,9 @@ } int cursor_col = st.cursor.col - st.firsty; - if (cursor_col >= buf.visible_cols) { + if (cursor_col >= buf.visibleCols()) { + return; // cursor not visible + } else if (cursor_col < 0) { return; // cursor not visible } @@ -2532,6 +2712,7 @@ glyph_gutter_width + debug_gutter_width; int rect_y = cursor_row * metrics.height; + // we _don't_ make cursor as wide as underlying character int rect_width = metrics.width; int rect_height = metrics.height - metrics.leading; if (has_focus) @@ -2545,7 +2726,7 @@ private boolean possiblyScrollDown() { /* * If cursor has moved below the scrollable region scroll down. - * Buffer manipulation is party done here or at the callsite if + * Buffer manipulation is partly done here or at the callsite if * 'true' is returned. */ @@ -2619,7 +2800,7 @@ * Perhaps SHOULD lock out sendChar() so user input doesn't interfere. */ private void reply(String str) { - // System.out.println("replying " + str); + // System.out.println("replying " + str); // NOI18N for (int sx = 0; sx < str.length(); sx++) sendChar(str.charAt(sx)); } @@ -2653,15 +2834,27 @@ public void op_char(char c) { if (debugOps()) - System.out.println("op_char('" + c + "')"); // NOI18N + System.out.println("op_char('" + c + "') = " + (int) c); // NOI18N // generic character printing Line l = cursor_line(); - if (!st.overstrike) - l.insertCharAt(buf, ' ', st.cursor.col, st.attr); + int insertion_col = l.cellToBuf(metrics, st.cursor.col); + if (debugOps()) { + System.out.println("op_char(): st.cursor.col " + st.cursor.col + // NOI18N + " insertion_col " + insertion_col); // NOI18N + } + if (!st.overstrike) { + // This just shifts stuff the actual character gets put in below. + l.insertCharAt(Term.this, ' ', insertion_col, st.attr); + } + + int cwidth = metrics.wcwidth(c); + if (l.isAboutToWrap() || + (cwidth > 1 && + st.cursor.col + cwidth > buf.visibleCols() && + !horizontally_scrollable)) { - if (l.isAboutToWrap()) { // 'wrap' the line if (debugOps()) System.out.println("\twrapping it"); // NOI18N @@ -2670,17 +2863,18 @@ op_line_feed(); op_carriage_return(); l = cursor_line(); + insertion_col = 0; // Fall thru } - l.setCharAt(buf, c, st.cursor.col, st.attr); // overstrike - st.cursor.col++; + l.setCharAt(Term.this, c, insertion_col, st.attr); // overstrike + st.cursor.col += cwidth; - if (st.cursor.col >= buf.visible_cols && !horizontally_scrollable) { + if (st.cursor.col >= buf.visibleCols() && !horizontally_scrollable) { if (debugOps()) System.out.println("\tabout to wrap"); // NOI18N l.setAboutToWrap(true); - st.cursor.col--; + st.cursor.col -= cwidth; } } @@ -2701,12 +2895,13 @@ System.out.println("op_back_space"); // NOI18N if (st.cursor.col > 0) { - if (! cursor_line().isAboutToWrap()) + if (! cursor_line().isAboutToWrap()) { st.cursor.col--; + } cursor_line().setAboutToWrap(false); // If we' backed up to column 0, maybe we need to consider - // whether the previous line was wrapped. Oldr xterms aren't + // whether the previous line was wrapped. Older xterms aren't // this clever, newer ones (Solaris 8+?) are. if (st.cursor.col == 0) { @@ -2719,8 +2914,24 @@ if (debugOps()) System.out.println("\tit is"); // NOI18N st.cursor.row--; - st.cursor.col = prev.length()-1; + + // The below is done in a roundabout way because BS doesn't + // really reduce length. So, suppose we went to the end with + // latin chars that makes the line 80 long. Then we backspace + // to column 78 and enter one 2-cell japanese character. Now + // the line is conceptually 79 long, but it still remembers + // the 80. So we don't use 'prev.length()' directly. + + // st.cursor.col = prev.bufToCell(metrics, prev.length()-1); + + int last_col = prev.cellToBuf(metrics, buf.visibleCols()-1); + st.cursor.col = prev.bufToCell(metrics, last_col); + prev.setWrapped(false); + + // The following isn't entirely correct when we backspaced + // over a multi-celled character. SHOULD either note + // what we BS'ed over or note the slop at the end of the line. prev.setAboutToWrap(true); } } @@ -2735,6 +2946,12 @@ if (debugOps()) System.out.println("op_line_feed"); // NOI18N Line last_line = cursor_line(); + /* DEBUG + if (last_line == null) { + Thread.dumpStack(); + printStats("last_line == null in op_line_feed()");// NOI18N + } + */ st.cursor.row++; if (possiblyScrollDown()) { buf.addLineAt(st.cursor.row); @@ -2743,8 +2960,14 @@ System.out.println("op_line_feed ADJUSTED"); // NOI18N } // have new line inherit cursorAtEnd - cursor_line().setAboutToWrap(last_line.isAboutToWrap()); + boolean atw = last_line.isAboutToWrap(); + cursor_line().setAboutToWrap(atw); last_line.setAboutToWrap(false); + + n_linefeeds++; + + // See repaint() for an explanation of this. + // repaint(false); } public void op_tab() { @@ -2756,15 +2979,20 @@ if (debugOps()) System.out.println("op_tab"); // NOI18N - if (st.cursor.col == buf.visible_cols-1 && !horizontally_scrollable) + if (st.cursor.col == buf.visibleCols()-1 && !horizontally_scrollable) return; - cursor_line().setCharAt(buf, ' ', st.cursor.col, st.attr); + Line l = cursor_line(); + int insert_col = l.cellToBuf(metrics, st.cursor.col); + l.setCharAt(Term.this, ' ', insert_col, st.attr); st.cursor.col++; - while ((st.cursor.col < buf.visible_cols-1 || horizontally_scrollable) && + insert_col++; + // no need to re-apply cellToBuf to cursor since we're only adding 1-wide ' ' + while ((st.cursor.col < buf.visibleCols()-1 || horizontally_scrollable) && (st.cursor.col % tab_size) != 0) { - cursor_line().setCharAt(buf, ' ', st.cursor.col, st.attr); + cursor_line().setCharAt(Term.this, ' ', insert_col, st.attr); st.cursor.col++; + insert_col++; } } @@ -2782,19 +3010,18 @@ Line l; while (count-- > 0) { - boolean old_cae = cursor_line().setAboutToWrap(false); + boolean old_atw = cursor_line().setAboutToWrap(false); // reverse of op_dl() // Rotate a line from bottom to top if (!do_margins) { l = buf.moveLineFromTo(buf.nlines-1, st.cursor.row); } else { - l = buf.moveLineFromTo(st.firstx + botMargin(), - st.cursor.row); + l = buf.moveLineFromTo(st.firstx + botMargin(), st.cursor.row); } l.reset(); - cursor_line().setAboutToWrap(old_cae); + cursor_line().setAboutToWrap(old_atw); } switch(sel.intersection(st.cursor.row - 1)) { @@ -2817,7 +3044,7 @@ if (debugOps()) System.out.println("op_bc(" + count + ")"); // NOI18N - while(count-- > 0) { + while (count-- > 0) { if (st.cursor.col <= 0) return; st.cursor.col--; @@ -2841,8 +3068,8 @@ // deal with overflow if (row > st.rows) row = st.rows; - if (col > buf.visible_cols) - col = buf.visible_cols; + if (col > buf.visibleCols()) + col = buf.visibleCols(); cursor_line().setAboutToWrap(false); st.cursor.row = beginx() + row - 1; @@ -2865,8 +3092,8 @@ if (debugOps()) System.out.println("op_ce"); // NOI18N - Line l = buf.lineAt(st.cursor.row); - l.clearToEndFrom(buf, st.cursor.col); + Line l = cursor_line(); + l.clearToEndFrom(Term.this, l.cellToBuf(metrics, st.cursor.col)); switch(sel.intersection(st.cursor.row)) { case Sel.INT_NONE: @@ -2912,7 +3139,7 @@ count = 1; Line l = cursor_line(); while (count-- > 0) - cursor_line().deleteCharAt(st.cursor.col); + l.deleteCharAt(l.cellToBuf(metrics, st.cursor.col)); } public void op_dl(int count) { @@ -2923,7 +3150,7 @@ Line l; while (count-- > 0) { - boolean old_cae = cursor_line().setAboutToWrap(false); + boolean old_atw = cursor_line().setAboutToWrap(false); // reverse of op_al() // Rotate a line from top to bottom @@ -2936,7 +3163,7 @@ } l.reset(); - cursor_line().setAboutToWrap(old_cae); + cursor_line().setAboutToWrap(old_atw); } switch(sel.intersection(st.cursor.row)) { @@ -2955,19 +3182,18 @@ } public void op_do(int count) { + // down count lines // SHOULD add a mode: {scroll, warp, stay} for cases where // cursor is on the bottom line. - // down count lines if (debugOps()) System.out.println("op_do(" + count + ") -- down"); // NOI18N - boolean old_cae = cursor_line().setAboutToWrap(false); + boolean old_atw = cursor_line().setAboutToWrap(false); while (count-- > 0) { st.cursor.row++; if (st.cursor.row >= buf.nlines) { - // st.cursor.row--; // don't go beyond bottomline // equivalent of op_newline: if (possiblyScrollDown()) { @@ -2978,7 +3204,7 @@ } } } - cursor_line().setAboutToWrap(old_cae); + cursor_line().setAboutToWrap(old_atw); } public void op_ho() { @@ -2995,8 +3221,12 @@ if (debugOps()) System.out.println("op_ic(" + count + ")"); // NOI18N - while (count-- > 0) - cursor_line().insertCharAt(buf, ' ', st.cursor.col, st.attr); + Line l = cursor_line(); + int insertion_col = l.cellToBuf(metrics, st.cursor.col); + while (count-- > 0) { + l.insertCharAt(Term.this, ' ', insertion_col, st.attr); + } + // SHOULD worry about line wrapping } public void op_nd(int count) { @@ -3004,15 +3234,17 @@ if (debugOps()) System.out.println("op_nd(" + count + ")"); // NOI18N + int vc = st.cursor.col; while (count-- > 0) { - st.cursor.col++; - if (st.cursor.col >= buf.visible_cols) { + vc++; + if (vc >= buf.visibleCols()) { if (debugOps()) System.out.println("\tbailing out at count " + count); // NOI18N - st.cursor.col--; - return; + vc--; + break; } } + st.cursor.col = vc; } public void op_up(int count) { @@ -3020,7 +3252,7 @@ if (debugOps()) System.out.println("op_up(" + count + ")"); // NOI18N - boolean old_cae = cursor_line().setAboutToWrap(false); + boolean old_atw = cursor_line().setAboutToWrap(false); Line l; while (count-- > 0) { st.cursor.row--; @@ -3030,14 +3262,13 @@ if (!do_margins) { l = buf.moveLineFromTo(buf.nlines-1, st.cursor.row); } else { - l = buf.moveLineFromTo(st.firstx + botMargin(), - st.cursor.row); + l = buf.moveLineFromTo(st.firstx + botMargin(), st.cursor.row); } l.reset(); // SHOULD note and do something about the selection? } } - cursor_line().setAboutToWrap(old_cae); + cursor_line().setAboutToWrap(old_atw); } public void op_sc() { @@ -3072,7 +3303,10 @@ } public void op_margin(int from, int to) { - // System.out.println("op_margin(" + from + ", " + to + ")"); // NOI18N + if (debugOps()) { + System.out.println("op_margin(" + from + ", " + // NOI18N + to + ")"); // NOI18N + } if (from < 0) top_margin = 0; @@ -3106,6 +3340,7 @@ String output1 = date_str + " Elapsed (sec): " + elapsed_str;// NOI18N String output2 = "putChar " + n_putchar + // NOI18N " putChars " + n_putchars + // NOI18N + " linefeeds " + n_linefeeds + // NOI18N " repaint " + n_repaint + // NOI18N " paint " + n_paint; // NOI18N @@ -3125,6 +3360,7 @@ last_time = time; n_putchar = 0; n_putchars = 0; + n_linefeeds = 0; n_paint = 0; n_repaint = 0; @@ -3133,7 +3369,7 @@ } public int op_get_width() { - return horizontally_scrollable? buf.totalCols(): buf.visible_cols; + return horizontally_scrollable? buf.totalCols(): buf.visibleCols(); } public int op_get_column() { @@ -3228,9 +3464,20 @@ // So extent has to be set to visible-range - 1: // It's important that we do this from within the AWT event thread. - // This is primarily ensured by the use of Swing.invokeAndWait() - // in StreamTerm. + if (SwingUtilities.isEventDispatchThread()) { + adjust_scrollbar_impl(); + } + else { + SwingUtilities.invokeLater(new Runnable() { + public void run() { + adjust_scrollbar_impl(); + } + }); + } + } + + private void adjust_scrollbar_impl() { if (vscroll_bar != null) { int value = st.firstx; int extent = st.rows-1; @@ -3245,11 +3492,11 @@ if (hscroll_bar != null && horizontally_scrollable) { int value = st.firsty; - int extent = buf.visible_cols-1; + int extent = buf.visibleCols()-1; int min = 0; int max; - if (buf.totalCols() <= buf.visible_cols) - max = buf.visible_cols - 1; + if (buf.totalCols() <= buf.visibleCols()) + max = buf.visibleCols() - 1; else max = buf.totalCols() - 1; hscroll_bar.setValues(value, extent, min, max); @@ -3260,7 +3507,7 @@ * Figure the pixel size of the screen based on various properties. */ private Dimension calculateSize() { - int dx = buf.visible_cols * metrics.width + + int dx = buf.visibleCols() * metrics.width + glyph_gutter_width + debug_gutter_width; int dy = st.rows * metrics.height; @@ -3306,7 +3553,7 @@ */ void sizeChanged(int newWidth, int newHeight) { - /* + /* DEBUG System.out.println("sizeChanged(newheight " + newHeight + // NOI18N ", newWidth " + newWidth + ")"); */ @@ -3379,10 +3626,102 @@ repaint(adjust_scrollbar); } + /** + * Model and or view settings have changed, redraw everything. + */ protected void repaint(boolean adjust_scrollbar) { /* - * Model and or view settings have changed, redraw everything. + * A long discussion on performance and smooth vs jump vs jerky + * scrolling ... (note: a lot of this is based on experiments with + * Term as a unix terminal emulator application as opposed to + * within the context of NetBeans). + * + * Term spends it's time between collecting and deciphering input + * and repainting the screen. Input processing always goes on, but + * screen repainitng can be done more or less often to trade off + * smoothness of scrolling vs speed. + * + * At one end is so-called smooth scrolling. This is where the + * screen is redrawn on every linefeed. That's a lot of painting. + * To get into that mode use the paintImmediately() below and + * uncomment the call to us in op_line_feed(). Also + * paintImmediately() doesn't really work unless the Screen is + * opaque. I think that is because the paint request comes + * to us and we don't forward it to screen; but it could be a + * Swing bug too. Term is very slow in this. For example I"ve + * time xterm and DtTerm dealing with "cat /etc/termcap" in 2-3 + * seconds while Term takes 20-25 seconds. Part of this is + * attributed to the fact that Term doesn't take advantage of + * bitBlitting when it's adding one line at a time and still + * redraws everything. However I'll make a case below that this + * isn't that important. + * + * Then there is so-called jump scrolling. In this regime terminal + * emulators redraw the screen "as time permits". This is in effect + * what the swing repaint manager helps with. Multiple repaint() + * requests translate to one actual paint(). With todays computers + * it's very hard to tell visually that you're jump scrolling + * things go by so fast (yes, even under Swing), so this is the + * preferred setup. + * Here term does a bit better. To deal with a cat'ed 100,000 + * line file DtTerm takes 8 seconds, while Term takes 22 seconds. + * (That's 3 times slower vs 8 times). From some measurements + * I've made the number of linefeeds per actual paints has + * ranged from > 100 to upper 30's. These numbers are sufficiently + * high that the whole screen has to be repained everytime. + * I.e. blitting to scroll and drawing only what's new isn't + * going to help here. To get reasonable jump-scrolling, you need + * to make sure that the Screen is opaque because if you don't + * you will get ... + * + * Jerky scrolling. If Term is not opaque, the number of actual + * paints per repaint() requests diminishes drastically. 'cat' of + * etc/termcap (once the code has been warmed up) sometimes causes + * a single refresh at the end in contrast to ~100 when Screen + * is opaque. Naturally Term in this mode can eat up input at + * a rate comparable to dtterm etc, but the jerkiness is very + * ugly. + * Opacity isn't the only criterion. Term, when embeded inside a + * tabbed pane (like it is in NetBeans) will also act as if it's + * opaque and you get more frequent refreshes, as in the + * jump-scrolling regime. But that was way too slow for the + * taste of NB users which is why OutputTab window calls us on a + * timer. That brings it's own jerkiness of a different sort. + * + * There is a third factor that contributes to slowness. If you + * just 'cat' a file you get the numbers I presneted above. But + * if you run an app that actually puts out the 100,000 lines + * some sort of timing interaction forces Term into near smooth + * scrolling and as a result things slow down a lot! For example, + * $ generate_100K_lines > /tmp/bag 00:08 sec + * $ cat /tmp/bag 00:20 sec + * $ generate_100K_lines 03:42 sec (opaque) + * $ generate_100K_lines 01:58 sec (!opaque) + * This happens even if the generating program is a lightweight + * native application. In fact I believe it is this effect that + * forced NB's OutputTab to adopt the timer. I believe there are two + * factors that contrinute to this. + * a) Running applications are line buffered so putChars(), with + * it's attendant repaint(), gets called once per line pushing + * us into the smooth scrolling regime. (But why then doesn't + * DtTerm suffer from this?) + * b) timeslicing gives enough time to the repaint manager such + * that it converts evey repaint() to a paint. + * I know (b) is a factor since if I "simulate" (a) by issueing + * repaints() from op_line_feed() while keeping this function from + * using paintImmediately() I don't get that many paints. + * The combined case has 44 paints per repaint as does simulated (a). + * So ain increased number of paints per repaint doesn't + * explain this. + * + * In the end, currently since jump scrolling is still not very + * fast and since NB has the timer anyway, Screen is not opaque. + * + * A useful quantitative measure is the number of linefeeds vs + * the number of repaint requests vs the number of actual paints. + * All these are collected and can be dumped via op_time() or + * printStats(). */ n_repaint++; @@ -3394,6 +3733,17 @@ // The following causes Screen.paint() to get called by the Swing // repaint manager which in turn calls back to term.paint(Graphics). screen.repaint(); + + + // The following should cause an immediate paint. It doesn't + // always though! + // I've found that for it to be effective Screen needs to be opaque. + + /* + NOTE: paintImmediately() is probably not the best thing to use. + // RepaintManager.currentManager(screen).paintDirtyRegions(); + screen.paintImmediately(0, 0, screen.getWidth(), screen.getHeight()); + */ } /* @@ -3560,7 +3910,7 @@ return interp; } - private Interp interp = new InterpANSI(ops); + private Interp interp = new InterpDumb(ops); // used to InterpANSI /** @@ -3644,29 +3994,43 @@ } /** - * Get cursor column (0-origin) + * Get cursor column in buffer coordinates (0-origin) */ public int getCursorCol() { - return st.cursor.col; + return cursor_line().cellToBuf(metrics, st.cursor.col); } /** - * Get (view) cursor coordinates. + * Get (absolute) cursor coordinates. *

    * The returned Coord is newly allocated and need not be cloned. */ public Coord getCursorCoord() { - return new Coord(new BCoord(st.cursor.row, st.cursor.col), firsta); + Line l = buf.lineAt(st.cursor.row); + return new Coord(new BCoord(st.cursor.row, + l.cellToBuf(metrics, st.cursor.col)), + firsta); + } + + /* + * + * Move the cursor to the given (absolute) coordinates + * + * @deprecated, replaced by{@link #setCursorCoord(Coord)} + */ + public void goTo(Coord coord) { + setCursorCoord(coord); } /** - * Move the cursor to the given (view) coordinates + * Move the cursor to the given (absolute) coordinates * SHOULD be setCursorCoord! */ - public void goTo(Coord coord) { + public void setCursorCoord(Coord coord) { Coord c = (Coord) coord.clone(); - c.clip(st.rows, buf.visible_cols, firsta); + c.clip(st.rows, buf.visibleCols(), firsta); st.cursor = c.toBCoord(firsta); + st.cursor.col = cursor_line().bufToCell(metrics, st.cursor.col); repaint(true); } @@ -4012,8 +4376,8 @@ public void columnRight(int n) { synchronized(this) { st.firsty += n; - if (st.firsty + buf.visible_cols > buf.totalCols()) - st.firsty = buf.totalCols() - buf.visible_cols; + if (st.firsty + buf.visibleCols() > buf.totalCols()) + st.firsty = buf.totalCols() - buf.visibleCols(); } repaint(true); } @@ -4030,6 +4394,13 @@ repaint(true); } + /** + * Return the cell width of the given character. + */ + public int charWidth(char c) { + return metrics.wcwidth(c); + } + /* * The following are overrides of JComponent/Component */ @@ -4050,7 +4421,7 @@ super.setFont(font); // This should invalidate us, which // ultimately will cause a repaint - /* + /* DEBUG System.out.println("Font info:"); // NOI18N System.out.println("\tlogical name: " + font.getName()); // NOI18N System.out.println("\tfamily name: " + font.getFamily()); // NOI18N @@ -4062,6 +4433,7 @@ updateScreenSize(); } + /** * Override of JComponent Index: TermStream.java =================================================================== RCS file: /cvs/core/libsrc/org/netbeans/lib/terminalemulator/TermStream.java,v retrieving revision 1.4 diff -u -r1.4 TermStream.java --- TermStream.java 4 Oct 2001 09:59:59 -0000 1.4 +++ TermStream.java 9 Apr 2002 01:03:53 -0000 @@ -48,6 +48,14 @@ this.toDTE = toDTE; } + void setTerm(Term term) { + this.term = term; + } + protected Term getTerm() { + return term; + } + private Term term; + // From world (DCE) to terminal (DTE) screen public abstract void flush(); Index: build.xml =================================================================== RCS file: /cvs/core/libsrc/org/netbeans/lib/terminalemulator/build.xml,v retrieving revision 1.2 diff -u -r1.2 build.xml --- build.xml 6 Nov 2001 02:14:41 -0000 1.2 +++ build.xml 9 Apr 2002 01:03:53 -0000 @@ -14,10 +14,14 @@ + @@ -28,6 +32,13 @@ compress="false" includes="org/netbeans/lib/terminalemulator/*.class" /> + + + + +