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.
+ *
* 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 @@
- 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.
+
+
+
+ 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.
-
- 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.
+
+
- 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.
- 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.
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.
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 @@
+ * 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: 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:
-
@@ -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.
Which is why we have ...
@@ -120,10 +180,9 @@