?? RIGHT := 110 ??
MODULE clm$process_redo_operation;

{
{ PURPOSE:
{   This module contains the FAP that implements redo of SCL commands from
{   the terminal and supports function keys at the SCL level.
{

?? NEWTITLE := 'Global Declarations', EJECT ??
?? PUSH (LISTEXT := ON) ??
*copyc amt$file_byte_address
*copyc clc$standard_file_names
*copyc lgt$log_read_activity
*copyc osd$virtual_address
*copyc ost$status
*copyc pmd$log_entries
*copyc pmd$system_log_interface
*copyc tut$input_ordinals
*copyc tut$subtable_pointers
?? POP ??
*copyc amp$access_method
*copyc amp$flush
*copyc amp$get_file_attributes
*copyc amp$get_next
*copyc amp$get_segment_pointer
*copyc amp$open
*copyc amp$put_next
*copyc amp$return
*copyc amp$rewind
*copyc amp$seek_direct
*copyc clp$read_variable
*copyc clp$scan_command_line
*copyc fsp$open_file
*copyc i#compare_collated
*copyc ifp$change_terminal_attributes
*copyc ifp$get_terminal_attributes
*copyc ifp$immediate_attribute_flush
*copyc ifp$store_term_conn_attributes
*copyc osv$lower_to_upper
*copyc pmp$load
*copyc pmp$load_from_library

  CONST
    cr = $CHAR (0d(16)),
    lf = $CHAR (0a(16)),
    crlf = cr CAT lf,
    max_commands = 2000,
    max_text_length = 256, { An arbitrary limit imposed by Redo.
    input_buffer_length = 80; {minimum allowed by NAM/VE}

  TYPE
    action_requested = (control_action, find_line),
    subtable_pointers = record
      header_ptr: ^tut$header,
      input_ptr: ^tut$input,
      output_ptr: ^tut$output,
      init_ptr: ^tut$init,
    recend,
    tty_command = record
      size: 0 .. 255,
      value: string (50),
    recend;

{ The following code duplicates the definition of lgv$control_codes_to_quest_mark which resides in module
{ lgm$global_log_manager and so is not accessible by Redo.  Redo would use osv$control_codes_to_quest_mark,
{ but it incorrectly converts the top 128 ASCII charcters to question marks.

  VAR
    control_codes_to_quest_mark: string (256) := '????????????' CAT
          '???????????????????? !"#$%&''()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTU' CAT
          'VWXYZ[\]^_`abcdefghijkl' CAT 'mnopqrstuvwxyz{|}~?' CAT $CHAR (128) CAT $CHAR (129) CAT
          $CHAR (130) CAT $CHAR (131) CAT $CHAR (132) CAT $CHAR (133) CAT $CHAR (134) CAT $CHAR (135) CAT
          $CHAR (136) CAT $CHAR (137) CAT $CHAR (138) CAT $CHAR (139) CAT $CHAR (140) CAT $CHAR (141) CAT
          $CHAR (142) CAT $CHAR (143) CAT $CHAR (144) CAT $CHAR (145) CAT $CHAR (146) CAT $CHAR (147) CAT
          $CHAR (148) CAT $CHAR (149) CAT $CHAR (150) CAT $CHAR (151) CAT $CHAR (152) CAT $CHAR (153) CAT
          $CHAR (154) CAT $CHAR (155) CAT $CHAR (156) CAT $CHAR (157) CAT $CHAR (158) CAT $CHAR (159) CAT
          $CHAR (160) CAT $CHAR (161) CAT $CHAR (162) CAT $CHAR (163) CAT $CHAR (164) CAT $CHAR (165) CAT
          $CHAR (166) CAT $CHAR (167) CAT $CHAR (168) CAT $CHAR (169) CAT $CHAR (170) CAT $CHAR (171) CAT
          $CHAR (172) CAT $CHAR (173) CAT $CHAR (174) CAT $CHAR (175) CAT $CHAR (176) CAT $CHAR (177) CAT
          $CHAR (178) CAT $CHAR (179) CAT $CHAR (180) CAT $CHAR (181) CAT $CHAR (182) CAT $CHAR (183) CAT
          $CHAR (184) CAT $CHAR (185) CAT $CHAR (186) CAT $CHAR (187) CAT $CHAR (188) CAT $CHAR (189) CAT
          $CHAR (190) CAT $CHAR (191) CAT $CHAR (192) CAT $CHAR (193) CAT $CHAR (194) CAT $CHAR (195) CAT
          $CHAR (196) CAT $CHAR (197) CAT $CHAR (198) CAT $CHAR (199) CAT $CHAR (200) CAT $CHAR (201) CAT
          $CHAR (202) CAT $CHAR (203) CAT $CHAR (204) CAT $CHAR (205) CAT $CHAR (206) CAT $CHAR (207) CAT
          $CHAR (208) CAT $CHAR (209) CAT $CHAR (210) CAT $CHAR (211) CAT $CHAR (212) CAT $CHAR (213) CAT
          $CHAR (214) CAT $CHAR (215) CAT $CHAR (216) CAT $CHAR (217) CAT $CHAR (218) CAT $CHAR (219) CAT
          $CHAR (220) CAT $CHAR (221) CAT $CHAR (222) CAT $CHAR (223) CAT $CHAR (224) CAT $CHAR (225) CAT
          $CHAR (226) CAT $CHAR (227) CAT $CHAR (228) CAT $CHAR (229) CAT $CHAR (230) CAT $CHAR (231) CAT
          $CHAR (232) CAT $CHAR (233) CAT $CHAR (234) CAT $CHAR (235) CAT $CHAR (236) CAT $CHAR (237) CAT
          $CHAR (238) CAT $CHAR (239) CAT $CHAR (240) CAT $CHAR (241) CAT $CHAR (242) CAT $CHAR (243) CAT
          $CHAR (244) CAT $CHAR (245) CAT $CHAR (246) CAT $CHAR (247) CAT $CHAR (248) CAT $CHAR (249) CAT
          $CHAR (250) CAT $CHAR (251) CAT $CHAR (252) CAT $CHAR (253) CAT $CHAR (254) CAT $CHAR (255);

  VAR
    backspace: tty_command,
    bkw_code: tty_command,
    bkw_word: ^char := NIL,
    clr: ^tty_command := NIL,
    clr_up: ^char := NIL,
    commands: ^array [1 .. max_commands] of ost$string := NIL,
    continued_command: boolean := FALSE,
    continued_marker: string (1) := '>',
    cr_delim_key_pushed: boolean := FALSE,
    cr_only: tty_command,
    current_column: integer,
    del_char: ^char := NIL,
    del_word: ^char := NIL,
    device_columns: integer, {number of columns on device
    device_input_length: amt$transfer_count,
    device_input_ndx: integer,
    device_overstrike_high: integer,
    device_overstrike_low: integer,
    device_start_input_search: integer,
    echo_off: array [1 .. 1] of ift$terminal_attribute := [[ifc$echoplex, FALSE]],
    echo_on: array [1 .. 1] of ift$terminal_attribute := [[ifc$echoplex, TRUE]],
    file_id: amt$file_identifier,
    full_duplex: boolean,
    full_duplex_on: ^boolean := NIL,
    fwd_word: ^char := NIL,
    has_cr_delimiter: boolean,
    ibm_3270: boolean := FALSE,
    input_buffer: string (input_buffer_length),
    in_file_id: amt$file_identifier,
    in_ptr: ^integer := NIL,
    insert_mode: boolean,
    insert_mode_on: ^boolean := NIL,
    insert_toggle: ^char := NIL,
    interrupt_char: ^char := NIL,
    job_log_file_id: amt$file_identifier,
    job_log_last_eoi_p: ^amt$file_byte_address := NIL,
    lfn: amt$local_file_name,
    log_ptr: amt$segment_pointer,
    move_to_end: ^char := NIL,
    move_to_start: ^char := NIL,
    non_xparent_file_id: amt$file_identifier,
    out_ptr: ^integer := NIL,
    private_reset_counter: integer,
    redo_is_shutdown:  ^boolean := NIL,
    redo_setup_done : [STATIC] boolean := FALSE,
    reset_counter: ^integer := NIL,
    reset_tdu: ^boolean := NIL,
    secondary_redo: boolean,
    set_line_mode_p: ^ost$string := NIL,
    set_screen_mode_p: ^ost$string := NIL,
    software_insert_default: boolean,
    software_insert_mode: boolean,
    static_shutdown: boolean := TRUE,
    status: ost$status,
    subtable: ^subtable_pointers := NIL,
    sysstat: ost$status,
    tt_header: ^tut$header := NIL,
    tt_init: ^tut$init := NIL,
    tt_input: ^tut$input := NIL,
    tt_input_upperbound: integer,
    tt_output: ^tut$output := NIL,
    up_arrow_code: tty_command;

?? TITLE := 'clp$redo_operation', EJECT ??

  PROCEDURE [XDCL] clp$redo_operation
    (    file_identifier: amt$file_identifier;
         call_block: amt$call_block;
         layer_number: amt$fap_layer_number;
     VAR status: ost$status);

    VAR
      ba: amt$file_byte_address,
      command_ptr: ^string (65535),
      file_id_temporary: amt$file_identifier,
      i: integer,
      first_time: [STATIC] boolean := TRUE;

    IF first_time THEN
      initialize_redo;

{   Bullet-proofing in case somehow the segment access file for the shared
{   redo log could not be opened.

      IF redo_is_shutdown = NIL THEN
        redo_is_shutdown := ^static_shutdown;
      IFEND;
      IF (NOT redo_is_shutdown^) AND (NOT secondary_redo) THEN

{     Go to screen mode and back to line mode. This is necessary on some
{     terminals to download function keys, set auto-CR on function keys, etc.

        setup_terminal_for_redo;
        setup_terminal_for_line_mode;
      IFEND;
      first_time := FALSE;
    IFEND;

  /read_command_from_terminal/
    WHILE TRUE DO

      IF ((redo_is_shutdown = NIL) OR redo_is_shutdown^) AND
            ((reset_tdu = NIL) OR (NOT reset_tdu^)) THEN

{ Redo is in shutdown mode and this is not an attempt to correct the terminal name.  Do no additional
{ processing of command input.  Amp$access_method must still be called so that commands may be entered in the
{ new task.

        amp$access_method (file_identifier, call_block, layer_number, status);
        RETURN;
      IFEND;

{An external agent (clm$enable_redo) sets the first item in the redo shared
{segment if the terminal definition information must be reestablished. Each
{copy of Redo (one in each active task) has a private_reset_counter that
{shows how many terminal type changes had been made when it loaded its
{set of the terminal definitions. If a child copy loads a new set, the
{shared counter will no longer match the parent's private value and thus
{the parent will know to reload the definitions.

      IF reset_tdu^ THEN
        IF in_ptr^ < 0 THEN
          in_ptr^ := 0;
        IFEND;
        full_duplex := full_duplex_on^;
        insert_mode := insert_mode_on^;
        software_insert_mode := insert_mode_on^;
        software_insert_default := insert_mode_on^;
        lfn := 'OUTPUT';
        IF full_duplex THEN
          ifp$change_terminal_attributes (lfn, echo_on, sysstat);
        ELSE
          ifp$change_terminal_attributes (lfn, echo_off, sysstat);
        IFEND;
        establish_terminal_commands;
        reset_counter^ := reset_counter^ +1;
        private_reset_counter := reset_counter^;
      ELSEIF reset_counter^ <> private_reset_counter THEN
        establish_terminal_commands;
        private_reset_counter := reset_counter^;
      IFEND;
      amp$access_method (file_identifier, call_block, layer_number, status);
      IF call_block.operation = amc$get_next_req THEN
        command_ptr := call_block.getn.working_storage_area;
        IF (call_block.getn.transfer_count^ = 0) OR redo_is_shutdown^ THEN
          RETURN;
        IFEND;

{   Check for control code input or BKW code at end of input line.

      /check_for_redo_call/
        BEGIN
          IF command_ptr^ (1) < ' ' THEN
            process_control_codes (call_block, control_action);
            EXIT /check_for_redo_call/;
          ELSEIF call_block.getn.transfer_count^ >= bkw_code.size THEN
            IF command_ptr^ (call_block.getn.transfer_count^ -bkw_code.size + 1, bkw_code.size) =
                  bkw_code.value (1, bkw_code.size) THEN
              call_block.getn.transfer_count^ := call_block.getn.transfer_count^ -bkw_code.size;
              process_control_codes (call_block, find_line);
              EXIT /check_for_redo_call/;
            IFEND;
          IFEND;
          IF call_block.getn.transfer_count^ >= up_arrow_code.size THEN
            IF command_ptr^ (call_block.getn.transfer_count^ -up_arrow_code.size + 1,
                  up_arrow_code.size) = up_arrow_code.value (1, up_arrow_code.size) THEN
              IF call_block.getn.transfer_count^ = up_arrow_code.size THEN
                process_control_codes (call_block, control_action);
              ELSE
                call_block.getn.transfer_count^ := call_block.getn.transfer_count^ -up_arrow_code.size;
                process_control_codes (call_block, find_line);
              IFEND;
            IFEND;
          IFEND;
        END /check_for_redo_call/;
        IF software_insert_mode OR insert_mode THEN
          IF redo_setup_done THEN
             turn_insert_mode_off;
             setup_terminal_for_line_mode;
             redo_setup_done := FALSE;
          IFEND;
        IFEND;

        IF call_block.getn.transfer_count^ = 0 THEN
          CYCLE /read_command_from_terminal/;
        IFEND;
      IFEND;
      RETURN;
    WHILEND /read_command_from_terminal/;

  PROCEND clp$redo_operation;
?? TITLE := 'convert_ordinal_to_ascii', EJECT ??

  PROCEDURE convert_ordinal_to_ascii
    (    ordinal: tut$input_ordinals;
     VAR ascii_seq: tty_command);

{Given a tuc$in_xxxx ordinal, this procedure will return the ascii
{codes sent by the terminal representing that sequence. If no
{sequence is defined for the terminal in the TDU file, ascii_seq.size
{will be zero. It will also be zero if the sequence has no unique
{mapping (as is the case for tuc$overstrike).

    CONST
      single_action_range = $CHAR (3),
      range = $CHAR (2),
      list = $CHAR (1),
      fail = $CHAR (0);

    VAR
      count: integer,
      cur_id: integer,
      i: integer,
      id: integer,
      j: integer,
      next_char: integer,
      num: integer,
      predecessor: integer,
      reversed_codes: string (50);


{   Get biased representation of ordinal in tt_input table.

    ascii_seq.size := 0;
    id := 16383 - $INTEGER (ordinal);
    i := 1;
    num := 1;

  /search_table/
    WHILE (i < tt_input_upperbound) AND (id <> 1) DO
      CASE tt_input^ [i] OF
      = single_action_range =
        predecessor := i;
        i := i + 5;
      = range =

{      IF reached most recently matched entry without finding pointer to it,
{      then presumably, the pointer points at one of the physically preceding
{      entries. Back up one and try to find it.

        IF i = id THEN {step back one leaf node}
          id := predecessor;
          i := 1;
          CYCLE /search_table/;
        IFEND;

        count := 2 * ($INTEGER (tt_input^ [i + 2]) - $INTEGER (tt_input^ [i + 1]));
        j := 0;
        WHILE j <= count DO
          cur_id := ($INTEGER (tt_input^ [i + 3 + j]) * 128) + $INTEGER (tt_input^ [i + 4 + j]);
          IF cur_id = id THEN {ordinal or pointer to previous entry found}
            reversed_codes (num) := $CHAR ($INTEGER (tt_input^ [i + 1]) + (j DIV 2));
            num := num + 1;
            id := i; {now look for something pointing to here}
            i := 1;
            CYCLE /search_table/;
          IFEND;
          j := j + 2;
        WHILEND;
        predecessor := i;
        i := i + 5 + count;
      = list =
        j := i; {save start of list location}

{      IF reached most recently matched entry without finding pointer to it,
{      then presumably, the pointer points at one of the physically preceding
{      entries. Back up one and try to find it.

        IF i = id THEN {step back one leaf node}
          id := predecessor;
          i := 1;
          CYCLE /search_table/;
        IFEND;

        count := $INTEGER (tt_input^ [i + 1]);
        i := i + 2;
        REPEAT
          cur_id := ($INTEGER (tt_input^ [i + 1]) * 128) + $INTEGER (tt_input^ [i + 2]);
          IF cur_id = id THEN
            reversed_codes (num) := tt_input^ [i];
            num := num + 1;
            id := j;
            i := 1;
            CYCLE /search_table/;
          ELSE
            i := i + 3;
            count := count - 1;
          IFEND;
        UNTIL count = 0;
        predecessor := j;
      = fail =

{      If reached most recently matched entry without finding pointer to it,
{      then presumably, the pointer points at one of the physically preceding
{      entries. Back up one and try to find it.

        IF i = id THEN {step back one leaf node}
          id := predecessor;
          i := 1;
          CYCLE /search_table/;
        IFEND;
        predecessor := i;
        i := i + 1;
      CASEND;
    WHILEND /search_table/;
    ascii_seq.size := num - 1;
    FOR i := 1 TO num - 1 DO
      ascii_seq.value (i) := reversed_codes (num - i);
    FOREND;

  PROCEND convert_ordinal_to_ascii;
?? TITLE := 'establish_terminal_commands', EJECT ??

  PROCEDURE establish_terminal_commands;

    TYPE
      ptr_changer = record
        case 0 .. 4 of
        = 0 =
          cell_ptr: ^cell,
        = 1 =
          input_ptr: ^tut$input,
        = 2 =
          output_ptr: ^tut$output,
        = 3 =
          header_ptr: ^tut$header,
        = 4 =
          init_ptr: ^tut$init,
        casend,
      recend;

    VAR
      data_name: ost$name,
      default_attributes: [STATIC] array [1 .. 2] of ift$terminal_attribute :=
            [[ifc$terminal_model, [ * , * ]], [ifc$page_width, * ]],
      fix_ptr: ptr_changer,
      len: integer,
      lfn: amt$local_file_name,
      start: integer;

    lfn := 'OUTPUT';
    default_attributes [1].key := ifc$terminal_model;
    ifp$get_terminal_attributes (lfn, default_attributes, status);
    IF NOT status.normal THEN
      shutdown_redo;
      RETURN;
    IFEND;
    device_columns := default_attributes [2].page_width;

    RESET log_ptr.sequence_pointer;
    NEXT reset_tdu IN log_ptr.sequence_pointer;
    NEXT full_duplex_on IN log_ptr.sequence_pointer;
    NEXT insert_mode_on IN log_ptr.sequence_pointer;
    NEXT redo_is_shutdown IN log_ptr.sequence_pointer;
    NEXT reset_counter IN log_ptr.sequence_pointer;
    NEXT fwd_word IN log_ptr.sequence_pointer;
    NEXT bkw_word IN log_ptr.sequence_pointer;
    NEXT del_word IN log_ptr.sequence_pointer;
    NEXT del_char IN log_ptr.sequence_pointer;
    NEXT clr_up IN log_ptr.sequence_pointer;
    NEXT move_to_end IN log_ptr.sequence_pointer;
    NEXT move_to_start IN log_ptr.sequence_pointer;
    NEXT insert_toggle IN log_ptr.sequence_pointer;
    NEXT interrupt_char IN log_ptr.sequence_pointer;
    NEXT clr IN log_ptr.sequence_pointer;
    NEXT subtable IN log_ptr.sequence_pointer;
    NEXT job_log_last_eoi_p IN log_ptr.sequence_pointer;
    NEXT set_line_mode_p IN log_ptr.sequence_pointer;
    NEXT set_screen_mode_p IN log_ptr.sequence_pointer;
    NEXT in_ptr IN log_ptr.sequence_pointer;
    NEXT out_ptr IN log_ptr.sequence_pointer;
    NEXT commands IN log_ptr.sequence_pointer;

{  Check to see if a copy of Redo is already running. This knowledge is used
{  to avoid reloading the TDU module, issuing screen/line mode transition, etc.

    secondary_redo := FALSE;
    IF in_ptr^ = 0 THEN
      in_ptr^ := 1;
      out_ptr^ := 1;
      redo_is_shutdown^ := FALSE;
    ELSEIF NOT reset_tdu^ THEN
      IF in_ptr^ >= 0 THEN
        secondary_redo := TRUE;
      IFEND;
    IFEND;

    reset_tdu^ := FALSE;
    IF NOT secondary_redo THEN
      data_name := 'CSV$';
      data_name (5, * ) := default_attributes [1].terminal_model.
            value (1, default_attributes [1].terminal_model.size);
      ibm_3270 := (default_attributes [1].terminal_model.size >= 8) AND
            (default_attributes [1].terminal_model.value (1, 8) = 'IBM_3270');
      IF (default_attributes [1].terminal_model.size = 0) OR ibm_3270 THEN
        shutdown_redo;
      ELSE
        load_tdu_module (data_name);
      IFEND;
      IF redo_is_shutdown^ THEN
        RETURN;
      IFEND;
      job_log_last_eoi_p^ := 0;
    ELSE {use tables previously loaded by initial Redo}
      fix_ptr.input_ptr := subtable^.input_ptr;
      fix_ptr.cell_ptr := #ADDRESS (#RING (^tt_input), #SEGMENT (subtable), #OFFSET (subtable^.input_ptr));
      tt_input := fix_ptr.input_ptr;
      fix_ptr.output_ptr := subtable^.output_ptr;
      fix_ptr.cell_ptr := #ADDRESS (#RING (^tt_output), #SEGMENT (subtable), #OFFSET (subtable^.output_ptr));
      tt_output := fix_ptr.output_ptr;
      fix_ptr.header_ptr := subtable^.header_ptr;
      fix_ptr.cell_ptr := #ADDRESS (#RING (^tt_header), #SEGMENT (subtable), #OFFSET (subtable^.header_ptr));
      tt_header := fix_ptr.header_ptr;
      fix_ptr.init_ptr := subtable^.init_ptr;
      fix_ptr.cell_ptr := #ADDRESS (#RING (^tt_init), #SEGMENT (subtable), #OFFSET (subtable^.init_ptr));
      tt_init := fix_ptr.init_ptr;
    IFEND;

    tt_input_upperbound := UPPERBOUND (tt_input^);
    IF ($INTEGER (tt_input^ [1]) = 3) AND {must be a single action range}
          ((16383 - (($INTEGER (tt_input^ [4]) * 128) + $INTEGER (tt_input^ [5]))) =
          $INTEGER (tuc$in_overstrike)) THEN
      device_overstrike_low := $INTEGER (tt_input^ [2]); {low bound of overstrikes}
      device_overstrike_high := $INTEGER (tt_input^ [3]);
      device_start_input_search := 6; {always skip first node}
    ELSE
      device_overstrike_low := 0;
      device_overstrike_high := 0;
      device_start_input_search := 1; {must start trie search at beginning}
    IFEND;

    IF device_columns = 0 THEN
      device_columns := tt_header^.screen_size [tuc$minimum_size_table].columns;
    IFEND;
    device_columns := device_columns - 3; {space for continued marker}
    convert_ordinal_to_ascii (tuc$in_backspace, backspace);
    IF backspace.size = 0 THEN {Terminal has no backspace code defined}
      convert_ordinal_to_ascii (tuc$in_cursor_left, backspace);
    IFEND;
    cr_only.size := tt_output^.ordinals [$INTEGER (tuc$out_return)].length;
    start := tt_output^.ordinals [$INTEGER (tuc$out_return)].start;
    cr_only.value := tt_output^.chars (start, cr_only.size);
    IF cr_only.size = 0 THEN {assume an ASCII CR will do the job}
      cr_only.size := 1;
      cr_only.value := cr;
    IFEND;
    clr^.size := tt_output^.ordinals [$INTEGER (tuc$out_erase_line_bol)].length;
    IF clr^.size <> 0 THEN
      start := tt_output^.ordinals [$INTEGER (tuc$out_erase_line_bol)].start;
      clr^.value (1, * ) := tt_output^.chars (start, clr^.size);
    ELSEIF tt_output^.ordinals [$INTEGER (tuc$out_erase_line_stay)].length <> 0 THEN
      clr^.size := tt_output^.ordinals [$INTEGER (tuc$out_erase_line_stay)].length;
      start := tt_output^.ordinals [$INTEGER (tuc$out_erase_line_stay)].start;
      clr^.value (1, * ) := tt_output^.chars (start, clr^.size);
      clr^.value (clr^.size + 1, * ) := cr_only.value (1, cr_only.size);
      clr^.size := clr^.size + cr_only.size;
    ELSEIF tt_output^.ordinals [$INTEGER (tuc$out_erase_end_of_line)].length <> 0 THEN
      clr^.value (1, * ) := cr_only.value (1, cr_only.size);
      len := tt_output^.ordinals [$INTEGER (tuc$out_erase_end_of_line)].length;
      start := tt_output^.ordinals [$INTEGER (tuc$out_erase_end_of_line)].start;
      clr^.value (cr_only.size + 1, * ) := tt_output^.chars (start, len);
      clr^.size := cr_only.size + len;
    IFEND;

{ Use BKW key if defined, otherwise use up arrow key.

    convert_ordinal_to_ascii (tuc$in_bkw, bkw_code);
    convert_ordinal_to_ascii (tuc$in_cursor_up, up_arrow_code);
    IF bkw_code.size = 0 THEN
      bkw_code := up_arrow_code;
    IFEND;

  PROCEND establish_terminal_commands;
?? TITLE := 'get_key', EJECT ??

  PROCEDURE get_key
    (VAR input_character: char;
     VAR ordinal: 0 .. tuc$in_max_ordinal);

{ get_key reads characters from the terminal and converts them
{ to an ordinal using the TDU table for the terminal.

    CONST
      single_action_range = $CHAR (3),
      range = $CHAR (2),
      list = $CHAR (1),
      fail = $CHAR (0);

    VAR
      action_found: boolean,
      ba: amt$file_byte_address,
      current_char: char,
      fpos: amt$file_position,
      list_counter: 0 .. 255,
      next_char: integer,
      sysstat: ost$status;

?? NEWTITLE := 'next_action', EJECT ??

    PROCEDURE [INLINE] next_action
      (    action_ndx: integer;
       VAR action_is_found: boolean;
       VAR action_ordinal: 0 .. tuc$in_max_ordinal;
       VAR next_character: integer);

      VAR
        action_id: integer;


      action_is_found := TRUE;
      action_id := ($INTEGER (tt_input^ [action_ndx]) * 128) + $INTEGER (tt_input^ [SUCC (action_ndx)]);
      IF action_id > tt_input_upperbound THEN { ordinal }
        action_ordinal := 0 - (action_id - 16383);
      ELSE { offset back into input array }
        next_character := action_id;
      IFEND;

    PROCEND next_action;

?? OLDTITLE, EJECT ??

{ ******  Map the input character in from the device, via terminal tables  ******

    ordinal := $INTEGER (tuc$in_no_input);
    input_character := $CHAR (0);
    next_char := 1; { start trie search at this point }

  /fill_ordinal/
    REPEAT { loop for enough chars to fill ordinal }

{ get character

      IF device_input_ndx < device_input_length THEN
        device_input_ndx := SUCC (device_input_ndx);
      ELSE
        amp$get_next (in_file_id, ^input_buffer, input_buffer_length, device_input_length, ba, fpos, sysstat);
        device_input_ndx := 1;
        IF device_input_length = 0 THEN
          IF cr_delim_key_pushed THEN
            cr_delim_key_pushed := FALSE;
            CYCLE /fill_ordinal/;
          IFEND;
          ordinal := $INTEGER (tuc$in_next); { 'reserved' ordinal for end-of-transctn}
          device_input_length := input_buffer_length; { force a read next time }
          device_input_ndx := device_input_length;
          EXIT /fill_ordinal/;
        IFEND;
      IFEND;
      current_char := input_buffer (device_input_ndx);

      IF (next_char = 1) AND ($INTEGER (current_char) >= device_overstrike_low) AND
            ($INTEGER (current_char) <= device_overstrike_high) THEN
        ordinal := $INTEGER (tuc$in_overstrike);
        input_character := current_char;
      ELSE {do trie search}
        action_found := FALSE;
        REPEAT { search for this char in the table }
          CASE tt_input^ [next_char] OF
          = single_action_range =
            IF (current_char >= tt_input^ [SUCC (next_char)]) AND (current_char <= tt_input^ [next_char + 2])
                  THEN
              next_action (next_char + 3, action_found, ordinal, next_char);
            ELSE
              next_char := next_char + 5;
            IFEND;
          = range =
            IF (current_char >= tt_input^ [SUCC (next_char)]) AND (current_char <= tt_input^ [next_char + 2])
                  THEN
              next_action (next_char + 3 + (($INTEGER (current_char) -
                    $INTEGER (tt_input^ [SUCC (next_char)])) * 2), action_found, ordinal, next_char);
            ELSE
              next_char := next_char + 3 + (2 * (1 + ($INTEGER (tt_input^ [next_char + 2]) -
                    $INTEGER (tt_input^ [SUCC (next_char)]))));
            IFEND;
          = list =
            list_counter := $INTEGER (tt_input^ [SUCC (next_char)]);
            next_char := next_char + 2;

          /list_loop/
            REPEAT
              IF current_char = tt_input^ [next_char] THEN
                next_action (SUCC (next_char), action_found, ordinal, next_char);
                EXIT /list_loop/;
              IFEND;
              next_char := next_char + 3;
              list_counter := PRED (list_counter);
            UNTIL list_counter = 0;
          = fail =
            ordinal := $INTEGER (tuc$in_cursor_pos_begin); {do more syntax analysis }
            EXIT /fill_ordinal/;
          ELSE
            setup_terminal_for_line_mode;
            RETURN;
          CASEND;
        UNTIL action_found;
      IFEND; {do trie search}
    UNTIL ordinal <> $INTEGER (tuc$in_no_input); {/fill_ordinal/}
    IF ordinal = $INTEGER (tuc$in_overstrike) THEN
      input_character := current_char;
    IFEND;

  PROCEND get_key;
?? TITLE := 'initialize_redo', EJECT ??

  PROCEDURE initialize_redo;

    VAR
      command_attributes: [STATIC] array [1 .. 1] of amt$get_item := [[ * , amc$user_info, ' ']],
      contains_data: boolean,
      file_attachment: [STATIC] array [1 .. 2] of fst$attachment_option :=
            [[fsc$access_and_share_modes, [fsc$specific_access_modes, $fst$file_access_options [fsc$read]],
            [fsc$specific_share_modes, $fst$file_access_options
            [fsc$append, fsc$modify, fsc$shorten, fsc$read]]], [fsc$private_read, FALSE]],
      i: integer,
      lfn: amt$local_file_name,
      local_file: boolean,
      old_file: boolean,
      open_attributes: [STATIC] array [1 .. 1] of amt$access_selection := [[amc$preset_value, 0]],
      file: string (40),
      ring_attributes: [STATIC] array [1 .. 1] of amt$access_selection :=
            [[amc$ring_attributes, [osc$user_ring_2, osc$user_ring_2, osc$user_ring_2]]],
      seg_file_id: amt$file_identifier,
      seg_file_ring_attributes: amt$ring_attributes;


{  Setup the input and output files used by Redo.
{  Create the normal use INPUT file connection and an extra one for
{  doing a zero timeout read to force out terminal attributes.
{  Create a transparent and non-transparent connection to the OUTPUT file
{  so that the non-transparent connection can be used to get out of
{  transparent mode when exiting Redo.
{  Open the log file as a source for previous commands.

    lfn := 'INPUT';
    amp$open (lfn, amc$record, ^open_attributes, in_file_id, sysstat);
    IF NOT sysstat.normal THEN
      shutdown_redo;
      RETURN;
    IFEND;
    lfn := 'OUTPUT';
    amp$open (lfn, amc$record, NIL, file_id, sysstat);
    IF NOT sysstat.normal THEN
      shutdown_redo;
      RETURN;
    IFEND;
    amp$open (lfn, amc$record, NIL, non_xparent_file_id, sysstat);
    IF NOT sysstat.normal THEN
      shutdown_redo;
      RETURN;
    IFEND;

{Extract Redo's parameters from the user_info file attribute of COMMAND.

    lfn := 'COMMAND';
    amp$get_file_attributes (lfn, command_attributes, local_file, old_file, contains_data, status);
    IF NOT status.normal THEN
      shutdown_redo;
      RETURN;
    IFEND;
    IF (command_attributes [1].source = amc$undefined_attribute) OR (command_attributes [1].user_info (1) <>
          'F') THEN
      full_duplex := FALSE;
    ELSE
      full_duplex := TRUE;
    IFEND;
    IF (command_attributes [1].source = amc$undefined_attribute) OR (command_attributes [1].user_info (2) <>
          'I') THEN
      software_insert_default := FALSE;
      software_insert_mode := FALSE;
    ELSE
      software_insert_default := TRUE;
      software_insert_mode := TRUE;
    IFEND;

{ Setup the shared segment access file for inter-task communication.

    lfn := 'clf$redo_log';
    amp$open (lfn, amc$segment, ^ring_attributes, seg_file_id, sysstat);
    IF NOT sysstat.normal THEN
      shutdown_redo;
      RETURN;
    IFEND;
    amp$get_segment_pointer (seg_file_id, amc$sequence_pointer, log_ptr, sysstat);
    IF NOT sysstat.normal THEN
      shutdown_redo;
      RETURN;
    IFEND;

    fsp$open_file (clc$job_log, amc$record, ^file_attachment, {default_creation_attributes} NIL,
          {mandated_creation_attributes} NIL, {attribute_validation} NIL, {attribute_override} NIL,
          job_log_file_id, sysstat);
    IF NOT sysstat.normal THEN
      shutdown_redo;
      RETURN;
    IFEND;

    establish_terminal_commands;

  PROCEND initialize_redo;
?? TITLE := 'load_TDU_module', EJECT ??

  PROCEDURE load_tdu_module
    (    data_name: ost$name);

    VAR
      appl_strings: ^tut$appl_string_pointer,
      data_address: pmt$loaded_address,
      dest_string: tut$application_name,
      i: integer,
      len: integer,
      lfn: amt$local_file_name,
      read_execute_attribs: [STATIC] array [1 .. 1] of amt$access_selection :=
            [[amc$access_mode, $pft$usage_selections [pfc$read, pfc$execute]]],
      seg_file_id: amt$file_identifier,
      seg_ptr: amt$segment_pointer,
      tt_subtable_pointers: ^tut$subtable_pointers;


{Load the TDU module for the terminal type selected by the user.

    pmp$load (data_name, pmc$data_address, data_address, status);
    IF NOT status.normal THEN
      clp$scan_command_line ('attf $system.tdu.terminal_definitions ' CAT 'clf$tdu_library', sysstat);
      IF NOT sysstat.normal THEN
        shutdown_redo;
        RETURN;
      IFEND;
      lfn := 'CLF$TDU_LIBRARY';
      amp$return (lfn, sysstat);
      amp$open (lfn, amc$segment, ^read_execute_attribs, seg_file_id, sysstat);
      IF NOT sysstat.normal THEN
        shutdown_redo;
        RETURN;
      IFEND;
      amp$get_segment_pointer (seg_file_id, amc$sequence_pointer, seg_ptr, sysstat);
      IF NOT sysstat.normal THEN
        shutdown_redo;
        RETURN;
      IFEND;
      pmp$load_from_library (data_name, #RING (^data_name), 0, pmc$data_address, seg_ptr.sequence_pointer,
            lfn, data_address, status);
      IF NOT status.normal THEN
        shutdown_redo;
        RETURN;
      IFEND;
    IFEND;

    tt_subtable_pointers := data_address.pointer_to_data;
    len := STRLENGTH (tt_subtable_pointers^.output^.chars);
    NEXT tt_output: [len] IN log_ptr.sequence_pointer;
    tt_output^ := tt_subtable_pointers^.output^;
    subtable^.output_ptr := tt_output;
    len := UPPERBOUND (tt_subtable_pointers^.input^);
    NEXT tt_input: [1 .. len] IN log_ptr.sequence_pointer;
    tt_input^ := tt_subtable_pointers^.input^;
    subtable^.input_ptr := tt_input;
    NEXT tt_header IN log_ptr.sequence_pointer;
    tt_header^ := tt_subtable_pointers^.header^;
    subtable^.header_ptr := tt_header;
    len := STRLENGTH (tt_subtable_pointers^.init^.chars);
    NEXT tt_init: [len] IN log_ptr.sequence_pointer;
    tt_init^ := tt_subtable_pointers^.init^;
    subtable^.init_ptr := tt_init;
    set_line_mode_p^.size := 0;
    set_screen_mode_p^.size := 0;
    fwd_word^ := $CHAR (06(16));
    bkw_word^ := $CHAR (02(16));
    del_word^ := $CHAR (04(16));
    del_char^ := $CHAR (7F(16));
    clr_up^ := $CHAR (15(16));
    move_to_end^ := $CHAR (05(16));
    move_to_start^ := $CHAR (16(16));
    insert_toggle^ := $CHAR (01(16));
    interrupt_char^ := $CHAR (14(16));
    appl_strings := tt_subtable_pointers^.appl_string_pointer;
    FOR i := 1 TO UPPERBOUND (appl_strings^) DO
      #TRANSLATE (osv$lower_to_upper, appl_strings^ [i].name, dest_string);
      IF dest_string = 'REDO_FORWARD_WORD' THEN
        fwd_word^ := tt_subtable_pointers^.appl_string_char^ (appl_strings^ [i].start);
      ELSEIF dest_string = 'REDO_BACKWARD_WORD' THEN
        bkw_word^ := tt_subtable_pointers^.appl_string_char^ (appl_strings^ [i].start);
      ELSEIF dest_string = 'REDO_DELETE_WORD' THEN
        del_word^ := tt_subtable_pointers^.appl_string_char^ (appl_strings^ [i].start);
      ELSEIF dest_string = 'REDO_DELETE_CHARACTER' THEN
        del_char^ := tt_subtable_pointers^.appl_string_char^ (appl_strings^ [i].start);
      ELSEIF dest_string = 'REDO_CLEAR_TO_CURSOR' THEN
        clr_up^ := tt_subtable_pointers^.appl_string_char^ (appl_strings^ [i].start);
      ELSEIF dest_string = 'REDO_CURSOR_TO_END' THEN
        move_to_end^ := tt_subtable_pointers^.appl_string_char^ (appl_strings^ [i].start);
      ELSEIF dest_string = 'REDO_CURSOR_TO_START' THEN
        move_to_start^ := tt_subtable_pointers^.appl_string_char^ (appl_strings^ [i].start);
      ELSEIF dest_string = 'REDO_INSERT_MODE_TOGGLE' THEN
        insert_toggle^ := tt_subtable_pointers^.appl_string_char^ (appl_strings^ [i].start);
      ELSEIF dest_string = 'REDO_INTERRUPT' THEN
        interrupt_char^ := tt_subtable_pointers^.appl_string_char^ (appl_strings^ [i].start);
      ELSEIF dest_string = 'REDO_SET_LINE_MODE' THEN
        set_line_mode_p^.value := tt_subtable_pointers^.appl_string_char^
              (appl_strings^ [i].start, appl_strings^ [i].length);
        set_line_mode_p^.size := appl_strings^ [i].length;
        IF appl_strings^ [i].length > 256 THEN
          set_line_mode_p^.size := 0;
        IFEND;
      ELSEIF dest_string = 'REDO_SET_SCREEN_MODE' THEN
        set_screen_mode_p^.value := tt_subtable_pointers^.appl_string_char^
              (appl_strings^ [i].start, appl_strings^ [i].length);
        set_screen_mode_p^.size := appl_strings^ [i].length;
        IF appl_strings^ [i].length > 256 THEN
          set_screen_mode_p^.size := 0;
        IFEND;
      IFEND;
    FOREND;

  PROCEND load_tdu_module;
?? TITLE := 'process_control_codes', EJECT ??

  PROCEDURE process_control_codes
    (    call_block: amt$call_block,
         action: action_requested);

    VAR
      ba: amt$file_byte_address,
      cell_ptr: ^cell,
      cmd_shown: 0 .. max_commands,
      column: integer,
      command: ost$string,
      command_string: string (max_text_length),
      count: amt$transfer_count,
      cr_lf: [STATIC] string (2) := crlf,
      in_transparent_mode: boolean,
      input_character: char,
      key_number: integer,
      key_string: string (4),
      left_column: integer,
      len: ^ost$string_size,
      m: integer,
      n: integer,
      no_fkey: [STATIC] string (25) := 'Function key not defined.',
      no_str: [STATIC] string (39) := 'Function key must be a string variable.',
      number_of_commands: integer,
      ordinal: 0 .. tuc$in_max_ordinal,
      scl_word_charset: [STATIC] set of char := ['#', '$', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
            '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S',
            'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '[', '\', ']', '^', '_', '`', 'a', 'b', 'c', 'd', 'e', 'f',
            'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y',
            'z', '{', '|', '}', '~'],
      string_ptr: ^string (max_text_length),
      string_ptr1: ^string (max_text_length),
      temp_string: string (max_text_length),
      v: clt$variable_reference;

?? NEWTITLE := 'backward_word', EJECT ??

    PROCEDURE backward_word;

      VAR
        i: 0 .. 1023,
        original_column: 0 .. 1023;


      IF column = 1 THEN
        left_column := 1;
        display_command;
        carriage_return;
        RETURN;
      IFEND;
      original_column := column;
      column := column - 1;

{  If character to left of cursor is a non-word character, skip it
{  and all preceding non-word characters until a word character or column
{  one is reached.

    /skip_non_word_characters/
      WHILE TRUE DO
        IF command.value (column) IN scl_word_charset THEN
          EXIT /skip_non_word_characters/;
        IFEND;
        column := column - 1;
        IF column = 0 THEN
          column := 1;
          left_column := 1;
          EXIT /skip_non_word_characters/;
        IFEND;
      WHILEND /skip_non_word_characters/;

{     If character is a word character, skip it and
{     all preceding word characters until a non-word character or column
{     one is found.

    /skip_back_over_word/
      WHILE TRUE DO
        column := column - 1;
        IF column = 0 THEN
          column := 1;
          left_column := 1;
          EXIT /skip_back_over_word/;
        IFEND;
        IF NOT (command.value (column) IN scl_word_charset) THEN
          column := column + 1;
          EXIT /skip_back_over_word/;
        IFEND;
      WHILEND /skip_back_over_word/;
      IF column < left_column THEN
        left_column := column - 20;
        display_command;
        move_cursor_to_column;
      ELSE
        FOR i := 1 TO (original_column - column) DO

{Use one character BS code to speed up cursor motion on ANSI terminals.

          IF backspace.size <> 0 THEN
            amp$put_next (file_id, ^backspace.value (1), backspace.size, ba, sysstat);
          ELSE
            output_ascii_from_ordinal (tuc$out_cursor_left);
          IFEND;
        FOREND;
      IFEND;

    PROCEND backward_word;
?? OLDTITLE, EJECT ??
?? NEWTITLE := 'Carriage_return', EJECT ??

    PROCEDURE [INLINE] carriage_return;


      amp$put_next (file_id, ^cr_only.value, cr_only.size, ba, sysstat);

    PROCEND carriage_return;
?? OLDTITLE, EJECT ??
?? NEWTITLE := 'Command_too_long', EJECT ??

    PROCEDURE command_text_too_long;

      VAR
        ba: amt$file_byte_address,
        message: [STATIC] string (62) :=
              ' Redo cannot be used with commands longer than 256 characters.';


      call_block.getn.transfer_count^ := 0;
      continued_command := FALSE;
{???  amp$put_next (file_id, ^clr^.value, clr^.size, ba, sysstat);
      setup_terminal_for_line_mode;
      amp$flush (file_id, osc$nowait, sysstat);
      amp$put_next (non_xparent_file_id, ^message, STRLENGTH (message), ba, sysstat);

    PROCEND command_text_too_long;
?? OLDTITLE, EJECT ??
?? NEWTITLE := 'cursor_bkw', EJECT ??

    PROCEDURE [INLINE] cursor_bkw;


      column := column - 1;
      IF (column - left_column + 1) < 1 THEN
        CASE tt_header^.cursor.behavior [tuc$move_past_left] OF
        = tuc$cursor_wrap_adjacent =
          output_ascii_from_ordinal (tuc$out_cursor_right);
        ELSE
          ;
        CASEND;
        IF left_column > 1 THEN
          left_column := left_column - 20;
          IF left_column < 1 THEN
            left_column := 1;
            column := 1;
          IFEND;
          display_command;
          move_cursor_to_column;
        ELSE
          column := 1;
        IFEND;
      IFEND;

    PROCEND cursor_bkw;
?? OLDTITLE, EJECT ??
?? NEWTITLE := 'cursor_forward', EJECT ??

    PROCEDURE [INLINE] cursor_forward;


      IF column < max_text_length THEN
        column := column + 1;
        IF (column - left_column + 1) > device_columns THEN
          left_column := left_column + 20;
          display_command;
          move_cursor_to_column;
        IFEND;
      ELSE

{ Don't move cursor beyond maximum command length.

        display_command;
        move_cursor_to_column;
      IFEND;

    PROCEND cursor_forward;
?? OLDTITLE, EJECT ??
?? NEWTITLE := 'delete_char_from_command', EJECT ??

    PROCEDURE [INLINE] delete_char_from_command;


      IF column <= command.size THEN
        temp_string := command.value (column + 1, command.size - column);
        command.value (column, * ) := temp_string;
        command.size := command.size - 1;
        IF command.size < 0 THEN
          command.size := 0;
        IFEND;
      IFEND;

    PROCEND delete_char_from_command;
?? OLDTITLE, EJECT ??
?? NEWTITLE := 'delete_word', EJECT ??

    PROCEDURE delete_word;


      IF column = 1 THEN
        left_column := 1;
        display_command;
        carriage_return;
        RETURN;
      IFEND;
      column := column - 1;

{  If character to left of cursor is a non-word character, delete it
{  and all preceding non-word characters until a word character or column
{  one is reached.

    /strip_non_word_characters/
      WHILE TRUE DO
        IF NOT (command.value (column) IN scl_word_charset) THEN
          delete_char_from_command;
          IF column > 1 THEN
            column := column - 1;
          ELSE
            left_column := 1;
            display_command;
            RETURN;
          IFEND;
        ELSE
          EXIT /strip_non_word_characters/;
        IFEND;
      WHILEND /strip_non_word_characters/;

{     If character to left of cursor is a word character, delete it and
{     all preceding word characters until a non-word character or column
{     one is found.

      WHILE TRUE DO
        IF command.value (column) IN scl_word_charset THEN
          delete_char_from_command;
          IF column > 1 THEN
            column := column - 1;
          ELSE
            left_column := 1;
            display_command;
            carriage_return;
            RETURN;
          IFEND;
        ELSE
          column := column + 1;
          IF column < left_column THEN
            left_column := column - 20;
            IF left_column < 1 THEN
              left_column := 1;
            IFEND;
          IFEND;
          display_command;
          move_cursor_to_column;
          RETURN;
        IFEND;
      WHILEND;

    PROCEND delete_word;
?? OLDTITLE, EJECT ??
?? NEWTITLE := 'display_command', EJECT ??

    PROCEDURE display_command;

      VAR
        length: integer;


      amp$put_next (file_id, ^clr^.value, clr^.size, ba, sysstat);
      length := min (command.size - left_column + 1, device_columns);
      amp$put_next (file_id, ^command.value (left_column), length, ba, sysstat);
      current_column := left_column + length - 1;

{ Save current_column for later use by move_cursor_to_column.

      IF (command.size - left_column + 1) > device_columns THEN
        amp$put_next (file_id, ^continued_marker, 1, ba, sysstat);
        current_column := current_column + 1
      IFEND;

    PROCEND display_command;
?? OLDTITLE, EJECT ??
?? NEWTITLE := 'echo_ascii_from_ordinal', EJECT ??

    PROCEDURE [INLINE] echo_ascii_from_ordinal
      (    ordinal: tut$output_ordinals);


      IF full_duplex THEN
        amp$put_next (file_id, ^tt_output^.chars (tt_output^.ordinals [$INTEGER (ordinal)].start),
              tt_output^.ordinals [$INTEGER (ordinal)].length, ba, sysstat);
      IFEND;

    PROCEND echo_ascii_from_ordinal;
?? OLDTITLE, EJECT ??
?? NEWTITLE := 'forward_word', EJECT ??

    PROCEDURE forward_word;

      VAR
        i: 0 .. 1023,
        original_column: 0 .. 1023;


      IF column >= command.size THEN
        RETURN;
      IFEND;
      original_column := column;

    /scan_a_word/
      BEGIN

      /scan_for_next_word/
        WHILE TRUE DO
          IF command.value (column) IN scl_word_charset THEN
            EXIT /scan_for_next_word/;
          IFEND;
          column := column + 1;
          IF column > command.size THEN
            column := command.size + 1;
            EXIT /scan_a_word/;
          IFEND;
        WHILEND /scan_for_next_word/;

      /scan_over_word/
        WHILE TRUE DO
          column := column + 1;
          IF column > command.size THEN
            column := command.size + 1;
            EXIT /scan_a_word/;
          IFEND;
          IF NOT (command.value (column) IN scl_word_charset) THEN
            EXIT /scan_over_word/;
          IFEND;
        WHILEND /scan_over_word/;

      /scan_to_word/
        WHILE TRUE DO
          column := column + 1;
          IF column > command.size THEN
            column := command.size + 1;
            EXIT /scan_a_word/;
          IFEND;
          IF command.value (column) IN scl_word_charset THEN
            EXIT /scan_to_word/;
          IFEND;
        WHILEND /scan_to_word/;

      END /scan_a_word/;

      IF (column - left_column + 1) > device_columns THEN
        left_column := column - device_columns + 20;
        display_command;
        move_cursor_to_column;
      ELSE
        amp$put_next (file_id, ^command.value (original_column), column - original_column, ba, sysstat);
      IFEND;

    PROCEND forward_word;
?? OLDTITLE, EJECT ??
?? NEWTITLE := 'min', EJECT ??

    FUNCTION [INLINE] min
      (    arg1: integer,
           arg2: integer): integer;


      IF arg1 < arg2 THEN
        min := arg1;
      ELSE
        min := arg2;
      IFEND;

    FUNCEND min;
?? OLDTITLE, EJECT ??
?? NEWTITLE := 'move_cursor_to_column', EJECT ??

    PROCEDURE [INLINE] move_cursor_to_column;

      VAR
        i: integer;


      IF column = left_column THEN
        carriage_return;
      ELSEIF (column - left_column) > (current_column - left_column) DIV 2 THEN
        WHILE column <= current_column DO
          amp$put_next (file_id, ^backspace.value, backspace.size, ba, sysstat);
          current_column := current_column - 1;
        WHILEND;
      ELSE
        carriage_return;
        IF column > left_column THEN
          amp$put_next (file_id, ^command.value (left_column), column - left_column, ba, sysstat);
        IFEND;
      IFEND;

    PROCEND move_cursor_to_column;
?? OLDTITLE, EJECT ??
?? NEWTITLE := 'output_ascii_from_ordinal', EJECT ??

    PROCEDURE [INLINE] output_ascii_from_ordinal
      (    ordinal: tut$output_ordinals);


      amp$put_next (file_id, ^tt_output^.chars (tt_output^.ordinals [$INTEGER (ordinal)].start),
            tt_output^.ordinals [$INTEGER (ordinal)].length, ba, sysstat);

    PROCEND output_ascii_from_ordinal;
?? OLDTITLE, EJECT ??

{  Update commands buffer from job log.

    update_commands;

    cmd_shown := in_ptr^;
    command.size := 0;
    column := 1;
    string_ptr := call_block.getn.working_storage_area;
    count := call_block.getn.transfer_count^;
    input_buffer := string_ptr^ (1, count);
    device_input_length := count;
    device_input_ndx := 0;
    software_insert_mode := software_insert_default;
    insert_mode := software_insert_default;
    cr_delim_key_pushed := FALSE;
    in_transparent_mode := FALSE;

{   If input line ended with BKW code, search backward for command
{   starting with the string preceding the BKW code.

    IF action = find_line THEN
      IF (in_ptr^ = out_ptr^) THEN {no search if nothing to search}
        output_ascii_from_ordinal (tuc$out_bell_nak);
        call_block.getn.transfer_count^ := 0;
        RETURN;
      IFEND;
      #TRANSLATE (osv$lower_to_upper, string_ptr^ (1, count), command_string);
      m := in_ptr^ -1;

    /search_for_command/
      WHILE TRUE DO

{       IF commands^[m].value (1, count) = command_string(1, count) THEN

        IF i#compare_collated (commands^ [m].value (1, count), command_string (1, count),
              osv$lower_to_upper) = 0 THEN
          command := commands^ [m];
          left_column := 1;
          cmd_shown := m;
          column := 1;
          device_input_length := 0;
          cr_delim_key_pushed := in_transparent_mode;
          in_transparent_mode := TRUE;
          setup_terminal_for_redo;
          redo_setup_done := TRUE;
          display_command;
          carriage_return;
          get_key (input_character, ordinal);
          EXIT /search_for_command/;
        ELSE
          IF (m = out_ptr^) OR (in_ptr^ = out_ptr^) THEN
            output_ascii_from_ordinal (tuc$out_bell_nak);
            call_block.getn.transfer_count^ := 0;
            RETURN;
          ELSE
            m := m - 1;
            IF m < 1 THEN
              m := max_commands;
            IFEND;
          IFEND;
        IFEND;
      WHILEND /search_for_command/;
    ELSE {control action - initiate only on bkw, cursor up, or F-keys.}
      get_key (input_character, ordinal);
      IF (ordinal <> $INTEGER (tuc$in_bkw)) AND (ordinal <> $INTEGER (tuc$in_cursor_up)) AND
            ((ordinal < $INTEGER (tuc$in_f1)) OR (ordinal > $INTEGER (tuc$in_f16_s))) THEN
        RETURN;
      IFEND;
      setup_terminal_for_redo;
      redo_setup_done := TRUE;
    IFEND;

{   Process user editing actions on the line.

  /process_user_redo/
    WHILE TRUE DO
      CASE ordinal OF
      = $INTEGER (tuc$in_overstrike) =
        IF ($INTEGER (input_character) < 32) OR ($INTEGER (input_character) = 7F(16)) THEN
          IF (input_character = interrupt_char^) OR (cmd_shown = in_ptr^) THEN
            amp$put_next (file_id, ^clr^.value, clr^.size, ba, sysstat);
            call_block.getn.transfer_count^ := 0;
            continued_command := FALSE;
            setup_terminal_for_line_mode;
            RETURN;
          ELSEIF input_character = del_word^ THEN
            delete_word;
          ELSEIF input_character = fwd_word^ THEN
            forward_word;
          ELSEIF input_character = bkw_word^ THEN
            backward_word;
          ELSEIF input_character = clr_up^ THEN
            temp_string := command.value (column, * );
            command.value (1, * ) := temp_string;
            command.size := command.size - column + 1;
            column := 1;
            display_command;
            carriage_return;
          ELSEIF input_character = insert_toggle^ THEN
            software_insert_mode := NOT software_insert_mode;
            insert_mode := software_insert_mode;
          ELSEIF input_character = del_char^ THEN
            IF column <> 1 THEN
              column := column - 1;
              delete_char_from_command;
            IFEND;
            display_command;
            move_cursor_to_column;
          ELSEIF input_character = move_to_end^ THEN
            IF command.size <  max_text_length THEN
              column := command.size + 1;
            ELSE

{ Don't move cursor beyond maximum command length.

              column := max_text_length;
            IFEND;

            IF column > device_columns THEN
              left_column := command.size - device_columns + 20;
            IFEND;
            display_command;
            move_cursor_to_column;
          ELSEIF input_character = move_to_start^ THEN
            column := 1;
            IF left_column <> 1 THEN
              left_column := 1;
              display_command;
            IFEND;
            carriage_return;
          ELSE {unknown control code -- refresh the line}
            display_command;
            move_cursor_to_column;
          IFEND;
        ELSE
          IF insert_mode THEN
            IF command.size < max_text_length THEN
              temp_string := command.value (column, * );
              command.value (column) := input_character;
              command.value (column + 1, * ) := temp_string;
              command.size := command.size + 1;
              column := column + 1;
              IF full_duplex THEN

{ Optimize insert operation to speed up ANSI type terminals.

                IF software_insert_mode AND (column <> command.size + 1) THEN
                  current_column := min (command.size - column + 1, device_columns - (column - left_column));
                  amp$put_next (file_id, ^command.value (column - 1), current_column + 1, ba, sysstat);
                  current_column := column + current_column - 1;
                  IF (command.size - column + 1) > device_columns THEN
                    amp$put_next (file_id, ^continued_marker, 1, ba, sysstat);
                    current_column := current_column + 1;
                  IFEND;
                  move_cursor_to_column;
                ELSE
                  amp$put_next (file_id, ^input_character, 1, ba, sysstat);
                IFEND;
              IFEND;
              column := column - 1;
              cursor_forward;
            ELSE

{ Don't extend command beyond maximum command length.

              command_text_too_long;
              RETURN;
            IFEND;
          ELSE
            IF column <= max_text_length THEN
              IF full_duplex THEN
                amp$put_next (file_id, ^input_character, 1, ba, sysstat);
              IFEND;
              command.value (column) := input_character;
              IF column > command.size THEN
                command.size := column;
              IFEND;
              cursor_forward;
            ELSE

{ Don't extend command beyond maximum command length.

              command_text_too_long;
              RETURN;
            IFEND;
          IFEND;
        IFEND;
      = $INTEGER (tuc$in_cursor_right) =
        IF column <= max_text_length THEN
          IF column > command.size THEN
            command.value (column) := ' ';
            command.size := column;
          IFEND;
          IF full_duplex THEN
            amp$put_next (file_id, ^command.value (column), 1, ba, sysstat);
          IFEND;
          cursor_forward;
        ELSE

{ Don't move cursor beyond maximum command length.

          display_command;
          move_cursor_to_column;
        IFEND;
      = $INTEGER (tuc$in_cursor_left), $INTEGER (tuc$in_backspace) =
        IF backspace.size <> 0 THEN
          IF full_duplex THEN
            amp$put_next (file_id, ^backspace.value (1), backspace.size, ba, sysstat);
          IFEND;
        ELSE
          echo_ascii_from_ordinal (tuc$out_cursor_left);
        IFEND;
        cursor_bkw;
      = $INTEGER (tuc$in_fwd), $INTEGER (tuc$in_cursor_down) =

{IF FWD beyond most recent command or empty buffer, restore line mode.

        IF (NOT full_duplex) AND (ordinal = $INTEGER (tuc$in_cursor_down)) THEN
          output_ascii_from_ordinal (tuc$out_cursor_up);
        IFEND;
        IF (cmd_shown = in_ptr^ -1) OR (in_ptr^ = out_ptr^) OR (cmd_shown = 0) THEN
          call_block.getn.transfer_count^ := 0;
          amp$put_next (file_id, ^clr^.value, clr^.size, ba, sysstat);
          setup_terminal_for_line_mode;
          RETURN;
        IFEND;

        cmd_shown := cmd_shown + 1;
        IF cmd_shown > max_commands THEN {handle buffer wraparound}
          cmd_shown := 1;
        IFEND;

        command := commands^ [cmd_shown];
        left_column := 1;

{   bypass tuc$in_next event if cr_delimited keys

        IF ordinal = $INTEGER (tuc$in_fwd) THEN
          cr_delim_key_pushed := TRUE;
        IFEND;
        display_command;
        carriage_return;
        column := 1;
      = $INTEGER (tuc$in_bkw), $INTEGER (tuc$in_cursor_up) =

{ If buffer empty, or can't back up further, ring bell.

        IF (NOT full_duplex) AND (ordinal = $INTEGER (tuc$in_cursor_up)) THEN
          IF in_transparent_mode THEN
            output_ascii_from_ordinal (tuc$out_cursor_down);
          IFEND;
        IFEND;
        IF cmd_shown = 0 THEN {BKW from F-key generated command}
          cmd_shown := in_ptr^;
        IFEND;
        IF (cmd_shown = out_ptr^) OR (in_ptr^ = out_ptr^) THEN
          output_ascii_from_ordinal (tuc$out_bell_nak);
          IF in_ptr^ = out_ptr^ THEN
            call_block.getn.transfer_count^ := 0;
            setup_terminal_for_line_mode;
            RETURN;
          IFEND;
        ELSE
          cmd_shown := cmd_shown - 1;
          IF cmd_shown < 1 THEN
            cmd_shown := max_commands;
          IFEND;
          command := commands^ [cmd_shown];
          left_column := 1;
        IFEND;

{   bypass tuc$in_next event if cr_delimited key

        IF ordinal = $INTEGER (tuc$in_bkw) THEN
          cr_delim_key_pushed := in_transparent_mode;
        IFEND;
        in_transparent_mode := TRUE;
        left_column := 1;
        column := 1;
        display_command;
        carriage_return;
      = $INTEGER (tuc$in_fwd_s) =
        forward_word;
        cr_delim_key_pushed := TRUE;
      = $INTEGER (tuc$in_up) =
        IF command.size < max_text_length THEN

{ Move cursor one column past the last character.

          column := command.size + 1;
        ELSE

{ Move cursor to the last character if already at the maximum command length.

          column := max_text_length;
        IFEND;
        cr_delim_key_pushed := TRUE;
        IF column > device_columns THEN
          left_column := command.size - device_columns + 20;
        IFEND;
        display_command;
        move_cursor_to_column;
      = $INTEGER (tuc$in_down) =
        column := 1;
        IF left_column <> 1 THEN
          left_column := 1;
          display_command;
        IFEND;
        carriage_return;
        cr_delim_key_pushed := TRUE;
      = $INTEGER (tuc$in_delete_char) =
        echo_ascii_from_ordinal (tuc$out_delete_char);
        delete_char_from_command;
        IF command.size >= device_columns THEN
          display_command;
          move_cursor_to_column;
        IFEND;
      = $INTEGER (tuc$in_insert_char) =
        IF command.size < max_text_length THEN
          echo_ascii_from_ordinal (tuc$out_insert_char);
          temp_string := command.value (column, command.size);
          command.value (column) := ' ';
          command.value (column + 1, * ) := temp_string;
          command.size := command.size + 1;
        ELSE

{ Do not insert a blank if already at the maximum command length.

          command_text_too_long;
          RETURN;
        IFEND;
      = $INTEGER (tuc$in_insert_mode_begin) =
        echo_ascii_from_ordinal (tuc$out_insert_mode_begin);
        insert_mode := TRUE;
      = $INTEGER (tuc$in_insert_mode_end) =
        echo_ascii_from_ordinal (tuc$out_insert_mode_end);
        insert_mode := FALSE;
      = $INTEGER (tuc$in_insert_mode_toggle) =
        echo_ascii_from_ordinal (tuc$out_insert_mode_toggle);
        insert_mode := NOT insert_mode;
      = $INTEGER (tuc$in_erase_end_of_line) =
        echo_ascii_from_ordinal (tuc$out_erase_end_of_line);
        command.size := column;
        command.value (column, * ) := ' ';
      = $INTEGER (tuc$in_erase_line_bol) =
        echo_ascii_from_ordinal (tuc$out_erase_line_bol);
        command.size := 0;
        command.value := ' ';
        column := 1;
        left_column := 1;
        display_command;
        carriage_return;
      = $INTEGER (tuc$in_erase_line_stay) =
        echo_ascii_from_ordinal (tuc$out_erase_line_stay);
        command.size := column;
        command.value := ' ';
        left_column := 1;
        display_command;
        carriage_return;
      = $INTEGER (tuc$in_erase_char) =
        echo_ascii_from_ordinal (tuc$out_erase_char);
        command.value (column - 1) := ' ';
        IF column - 1 = command.size THEN
          command.size := command.size - 1;
        IFEND;
        cursor_bkw;
      = $INTEGER (tuc$in_back), $INTEGER (tuc$in_stop), $INTEGER (tuc$in_stop_s) =
        amp$put_next (file_id, ^clr^.value, clr^.size, ba, sysstat);
        IF ordinal = $INTEGER (tuc$in_back) THEN {strip off CR from BACK}
          get_key (input_character, ordinal);
        IFEND;
        in_transparent_mode := FALSE;
        call_block.getn.transfer_count^ := 0;
        setup_terminal_for_line_mode;
        continued_command := FALSE;
        RETURN;
      = $INTEGER (tuc$in_return) =
        column := 1;
      = $INTEGER (tuc$in_next) =
        IF NOT cr_delim_key_pushed THEN
          string_ptr := call_block.getn.working_storage_area;
          string_ptr^ (1, command.size) := command.value (1, command.size);
          call_block.getn.transfer_count^ := command.size;
          setup_terminal_for_line_mode;
          amp$put_next (non_xparent_file_id, ^cr_only.value, cr_only.size, ba, sysstat);
          amp$flush (non_xparent_file_id, osc$nowait, sysstat);
          RETURN;
        ELSE
          cr_delim_key_pushed := FALSE;
        IFEND;
      = $INTEGER (tuc$in_tab_forward) =
        establish_terminal_commands;
      = $INTEGER (tuc$in_f1) .. $INTEGER (tuc$in_f16_s) =
        key_number := ordinal - $INTEGER (tuc$in_f1) + 1;
        IF key_number > 16 THEN
          key_string := 'SF';
          key_number := key_number - 16;
          n := 3;
        ELSE
          key_string := 'F';
          n := 2;
        IFEND;
        IF key_number >= 10 THEN
          key_string (n) := '1';
          n := n + 1;
          key_number := key_number - 10;
        IFEND;
        key_string (n) := $CHAR (key_number + $INTEGER ('0'));
        clp$read_variable (key_string (1, n), v, sysstat);
        IF sysstat.normal THEN
          IF v.value.kind <> clc$string_value THEN
            amp$put_next (file_id, ^no_str, STRLENGTH (no_str), ba, sysstat);
            setup_terminal_for_line_mode;
            amp$put_next (non_xparent_file_id, ^cr_only.value, cr_only.size, ba, sysstat);
            amp$flush (non_xparent_file_id, osc$nowait, sysstat);
            call_block.getn.transfer_count^ := 0;
            RETURN;
          IFEND;
          string_ptr := call_block.getn.working_storage_area;
          cell_ptr := v.value.string_value;
          string_ptr1 := cell_ptr;
          n := 1;
          len := #LOC (v.value.string_value^ [n]);
          n := n + #SIZE (len^);
          IF (len^ > 1) AND (string_ptr1^ (n + len^ -2, 2) = '..') THEN
            temp_string := command.value (column, *);
            command.value (column, * ) := string_ptr1^ (n, len^ -2);
            command.value (column + len^ -2, * ) := temp_string;
            command.size := command.size + len^ -2;
            column := column + len^ -2;
          ELSE
            temp_string := command.value (column, command.size);
            command.value (column, * ) := string_ptr1^ (n, len^);
            command.value (column + len^, * ) := temp_string;
            command.size := command.size + len^;
            column := column + len^;
          IFEND;
          IF NOT in_transparent_mode THEN
            cmd_shown := 0; {flag command line not in commands buffer}
          IFEND;
          left_column := 1;
          IF (len^ > 1) AND (string_ptr1^ (n + len^ -2, 2) = '..') THEN
            IF command.size > device_columns THEN
              left_column := command.size - device_columns + 20;
            IFEND;
            display_command;
            move_cursor_to_column;
            IF in_transparent_mode THEN
              cr_delim_key_pushed := TRUE;
            IFEND;
          ELSE
            string_ptr^ (1, command.size) := command.value (1, command.size);
            call_block.getn.transfer_count^ := command.size;
            amp$put_next (file_id, ^command.value (1), command.size, ba, sysstat);
            setup_terminal_for_line_mode;
            amp$put_next (non_xparent_file_id, ^cr_only.value, cr_only.size, ba, sysstat);
            amp$flush (non_xparent_file_id, osc$nowait, sysstat);
            RETURN;
          IFEND;
          in_transparent_mode := TRUE;
        ELSE

{ If no function key defined, issue a message

          amp$put_next (file_id, ^no_fkey, STRLENGTH (no_fkey), ba, sysstat);
          setup_terminal_for_line_mode;
          amp$put_next (non_xparent_file_id, ^cr_only.value, cr_only.size, ba, sysstat);
          amp$flush (non_xparent_file_id, osc$nowait, sysstat);
          call_block.getn.transfer_count^ := 0;
          RETURN;
        IFEND;
      ELSE {force redisplay of current command if in doubt}
        IF cmd_shown <> in_ptr^ THEN
          display_command;
          move_cursor_to_column;
        ELSE
          amp$put_next (file_id, ^clr^.value, clr^.size, ba, sysstat);
          call_block.getn.transfer_count^ := 0;
          setup_terminal_for_line_mode;
          RETURN;
        IFEND;
      CASEND;
      get_key (input_character, ordinal);
    WHILEND /process_user_redo/;

  PROCEND process_control_codes;
?? TITLE := 'setup_terminal_for_line_mode', EJECT ??

  PROCEDURE setup_terminal_for_line_mode;

    VAR
      ba: amt$file_byte_address,
      fpos: amt$file_position,
      inbuf: string (1),
      lfn: amt$local_file_name,
      non_xparent_connect_attributes: [STATIC] array [1 .. 8] of ift$connection_attribute :=
            [[ifc$input_block_size, input_buffer_length], [ifc$input_editing_mode, ifc$normal_edit],
            [ifc$trans_character_mode, ifc$trans_char_forward],
            [ifc$trans_timeout_mode, ifc$no_trans_timeout], [ifc$trans_length_mode, ifc$trans_len_forward],
            [ifc$trans_message_length, input_buffer_length], [ifc$trans_forward_character,
            [1, $CHAR (0D(16))]], [ifc$trans_terminate_character, [1, $CHAR (00(16))]]];

    lfn := 'OUTPUT';
    IF full_duplex THEN
      ifp$change_terminal_attributes (lfn, echo_on, sysstat);
    IFEND;
    IF (set_line_mode_p <> NIL) AND (set_line_mode_p^.size > 0) THEN

{ An application string was defined in the terminal definition to set line mode.

      amp$put_next (file_id, ^set_line_mode_p^.value, set_line_mode_p^.size, ba, sysstat);
    ELSE
      amp$put_next (file_id, ^tt_output^.chars (tt_output^.ordinals [$INTEGER (tuc$out_set_line_mode)].start),
            tt_output^.ordinals [$INTEGER (tuc$out_set_line_mode)].length, ba, sysstat);
    IFEND;

{  Establish the terminal attributes to be line mode, non hot key.

    ifp$store_term_conn_attributes (non_xparent_file_id, non_xparent_connect_attributes, sysstat);
    IF NOT sysstat.normal THEN
      shutdown_redo;
      RETURN;
    IFEND;
    ifp$immediate_attribute_flush (non_xparent_file_id, sysstat);
    IF NOT sysstat.normal THEN
      shutdown_redo;
      RETURN;
    IFEND;

  PROCEND setup_terminal_for_line_mode;
?? TITLE := 'setup_terminal_for_redo', EJECT ??

  PROCEDURE setup_terminal_for_redo;

    VAR
      ba: amt$file_byte_address,
      connect_attributes: [STATIC] array [1 .. 8] of ift$connection_attribute :=
            [[ifc$input_block_size, input_buffer_length], [ifc$input_editing_mode, ifc$trans_edit],
            [ifc$input_output_mode, ifc$unsolicited_output], [ifc$trans_character_mode, ifc$no_trans_char],
            [ifc$trans_timeout_mode, ifc$no_trans_timeout], [ifc$trans_length_mode, ifc$trans_len_forward],
            [ifc$trans_message_length, 1], [ifc$trans_terminate_character, [1, $CHAR (00(16))]]],
      lfn: amt$local_file_name;


{  Establish the terminal attributes for Redo's files.

    ifp$store_term_conn_attributes (file_id, connect_attributes, sysstat);
    IF NOT sysstat.normal THEN
      shutdown_redo;
      RETURN;
    IFEND;
    ifp$store_term_conn_attributes (in_file_id, connect_attributes, sysstat);
    IF NOT sysstat.normal THEN
      shutdown_redo;
      RETURN;
    IFEND;
    ifp$immediate_attribute_flush (in_file_id, sysstat);
    IF NOT sysstat.normal THEN
      shutdown_redo;
      RETURN;
    IFEND;
    lfn := 'OUTPUT';
    IF full_duplex THEN
      ifp$change_terminal_attributes (lfn, echo_off, sysstat);
    IFEND;
    IF (set_screen_mode_p <> NIL) AND (set_screen_mode_p^.size > 0) THEN

{ An application string was defined in the terminal definition to set screen mode.

      amp$put_next (file_id, ^set_screen_mode_p^.value, set_screen_mode_p^.size, ba, sysstat);
    ELSE
      amp$put_next (file_id, ^tt_output^.chars (tt_output^.ordinals [$INTEGER (tuc$out_set_screen_mode)].
            start), tt_output^.ordinals [$INTEGER (tuc$out_set_screen_mode)].length, ba, sysstat);
    IFEND;

  PROCEND setup_terminal_for_redo;
?? TITLE := 'shutdown_redo', EJECT ??

  PROCEDURE shutdown_redo;

    VAR
      ba: amt$file_byte_address,
      message_p: ^string ( * ),
      redo_turned_off: [STATIC] string (71) :=
              ' Redo is disabled for the current task because of file access problems.',
      unsupported_terminal: [STATIC] string (42) :=
              ' Redo does not support IBM_3270 terminals.',
      unknown_terminal: [STATIC] string (60) :=
              ' Redo not enabled due to missing or undefined terminal name.';


    IF redo_is_shutdown = NIL THEN
      redo_is_shutdown := ^static_shutdown;
    IFEND;
    redo_is_shutdown^ := TRUE;
    IF in_ptr = NIL THEN
      message_p := ^redo_turned_off;
    ELSE
      IF ibm_3270 THEN
        message_p := ^unsupported_terminal;
      ELSE
        message_p := ^unknown_terminal;
      IFEND;
      in_ptr^ := -1; { To keep secondary redo detection from using bad TDU pointers.
    IFEND;
    amp$put_next (file_id, message_p, STRLENGTH (message_p^), ba, sysstat);

  PROCEND shutdown_redo;
?? TITLE := 'turn_insert_mode_off', EJECT ??

  PROCEDURE turn_insert_mode_off;

    VAR
      ba: amt$file_byte_address;


    IF tt_output^.ordinals [$INTEGER (tuc$out_insert_mode_end)].length <> 0 THEN
      amp$put_next (file_id, ^tt_output^.chars (tt_output^.ordinals [$INTEGER (tuc$out_insert_mode_end)].
            start), tt_output^.ordinals [$INTEGER (tuc$out_insert_mode_end)].length, ba, sysstat);
    ELSE
      amp$put_next (file_id, ^tt_output^.chars (tt_output^.ordinals [$INTEGER (tuc$out_insert_mode_toggle)].
            start), tt_output^.ordinals [$INTEGER (tuc$out_insert_mode_toggle)].length, ba, sysstat);
    IFEND;
    insert_mode := FALSE;

  PROCEND turn_insert_mode_off;
?? TITLE := 'update_commands', EJECT ??

{  PURPOSE:
{     This procedure updates the command buffer by reading job log entries that
{     have been added since the last time this procedure was called.
{
{  DESIGN:
{     This procedure opens the log file, positions the log at the last address
{     read and reads the remainder of the file.  Each log entry of origin type
{     'CI' is added to the command buffer.
{
{  NOTE:
{     The job log is used as the source of previous commands to ensure that Redo
{     does not display the of value of secure parameters.

  PROCEDURE update_commands;

    CONST
      command_too_long = '" Redo cannot handle commands longer than 256 characters.',
      command_too_long_length = 57;

    VAR
      byte_address: amt$file_byte_address,
      current_length: amt$transfer_count,
      file_position: amt$file_position,
      prefix_length: integer,
      text_line: ^pmt$log_msg_text,
      text_line_length: integer,
      text_p: ^pmt$job_log_entry;

?? NEWTITLE := 'update_command_buffer_pointers', EJECT ??

    PROCEDURE [INLINE] update_command_buffer_pointers;


      IF in_ptr^ < max_commands THEN
        in_ptr^ := in_ptr^ +1;
      ELSE
        in_ptr^ := 1;
      IFEND;
      IF in_ptr^ = out_ptr^ THEN

{ Discard the oldest command because there are no unused entries.

        IF out_ptr^ < max_commands THEN
          out_ptr^ := out_ptr^ +1;
        ELSE
          out_ptr^ := 1;
        IFEND;
      IFEND;

    PROCEND update_command_buffer_pointers;
?? OLDTITLE, EJECT ??

    PUSH text_p: [lgc$log_entry_size_limit];
    IF text_p = NIL THEN
      RETURN;
    IFEND;

    prefix_length := #SIZE (pmt$job_log_entry: [0]);

    amp$seek_direct (job_log_file_id, job_log_last_eoi_p^, sysstat);
    IF NOT sysstat.normal THEN
      RETURN;
    IFEND;

  /get_log_records/
    WHILE TRUE DO

      amp$get_next (job_log_file_id, text_p, lgc$log_entry_size_limit, current_length, byte_address,
            file_position, sysstat);
      IF NOT sysstat.normal THEN
        EXIT /get_log_records/;
      IFEND;

      IF (current_length <= prefix_length) OR (job_log_last_eoi_p^ = byte_address) THEN
        job_log_last_eoi_p^ := byte_address;
        CYCLE /get_log_records/;
      IFEND;

      job_log_last_eoi_p^ := byte_address;
      IF text_p^.origin = 'CI' THEN

{  The string 'CI' corresponds to pmc$msg_origin_command which is not accessible to Redo.

        text_line_length := current_length - prefix_length;
        IF text_line_length > max_text_length THEN
          commands^ [in_ptr^].size := command_too_long_length;
          commands^ [in_ptr^].value := command_too_long;
          update_command_buffer_pointers;
        ELSEIF text_p^.text (1, text_line_length) <> '' THEN
          commands^ [in_ptr^].size := text_line_length;
          #TRANSLATE (control_codes_to_quest_mark, text_p^.text (1, text_line_length),
                commands^ [in_ptr^].value (1, text_line_length));
          update_command_buffer_pointers;
        IFEND;
      IFEND;
    WHILEND /get_log_records/;

  PROCEND update_commands;

MODEND clm$process_redo_operation;
