!++
! FILENAME: MATCHING.TPU
! FUNCTION: This file contains procedures for finding and manipulating matching
!           strings.
! AUTHOR:   Steven K. Shapiro, (C) Copyright SKS Enterprises, Austin TX.
!                                  All Rights Reserved.
!
!           The format, structure and contents of this file are the sole
!           property  of Steven K. Shapiro  and are  copyrighted to  SKS
!           Enterprises, Austin Texas.
!
!           The information may be freely distributed, used and modified
!           provided  that the  information in this  header block is not
!           changed, altered, disturbed or modified in any way.
!
! DATE:     26-AUG-1987 Original.
! HISTORY:  current.
! CONTENTS:
!           eve_find_matching
!           evedt_match (match_chars, quote_chars)
!           evedt_display_line
!           eve_set_matching(the_arg)
!           eve_set_nomatching(the_arg)
!           evedt_insert_matched
!           eve_find_be_match
!
!23456789A123456789B123456789C123456789D123456789E123456789F123456789G123456789H
!--
!*----------------------------------------------------------------------------*!

procedure matching_module_ident 

  local file_date,
        module_vers;

  file_date := "-<( 20-DEC-1988 15:31:35.38 )>-";
  module_vers := substr(file_date,5,2) +
                 substr(file_date,8,3) +
                 substr(file_date,14,2) +
                 substr(file_date,17,5) ;

  return module_vers;

endprocedure;

!*----------------------------------------------------------------------------*!
!
! This procedure will find the match to the current character.
! Valid matches are:
! evedt_matchable_open   := ([{<;
! evedt_matchable_close  := )]}>;

procedure eve_find_matching

local start_position,           ! Marker - current cursor position
      found_position,           ! Marker - position of found matching char
      right_matches,            ! Integer - number of opens to close
      left_matches,             ! Integer - number of closes to open
      all_chars,                ! String - open and close match_chars
      match_pattern,            ! Pattern - any (all_chars)
      match_position,           ! Marker - current position during searches
      target,
      match_chars,
      open_char,
      close_char;

   on_error
    [TPU$_CONTROLC]:
	eve$learn_abort;
	abort;
    [TPU$_STRNOTFOUND]:
        message ("No matching character found.");
        position (start_position);
	abort;
    [OTHERWISE]:
	abort;
  endon_error;

  target := current_character;

  open_type := index(evedt_matchable_open,target);

  if open_type = 0 then
    close_type := index(evedt_matchable_close,target);

    if close_type = 0 then
      message ( ">" + target + "<" + " is not a matchable character. " +
                "Valid characters are ( [ { < > } ] )");
    else  ! search for matching open char
      close_char := target;
      open_char  := substr(evedt_matchable_open,close_type,1);

      start_position := mark (none);
      right_matches := 1;
      move_horizontal (-1);
      match_chars := open_char + close_char;
      all_chars := open_char + close_char;
      match_pattern := any (all_chars);

      loop        ! looking for open char so search backwards
        match_position := search (match_pattern, reverse);
        exitif match_position = 0;
        position (match_position);

        if current_character = substr (match_chars, 1, 1) then
          right_matches := right_matches - 1;
          move_horizontal (-1);
        else
          if current_character = substr (match_chars, 2, 1) then
            right_matches := right_matches + 1;
            move_horizontal (-1);
          endif;
        endif;

        exitif right_matches = 0;
      endloop;

      if right_matches = 0 then
        move_horizontal (1);
        found_position := mark(none);
        match_range := create_range(found_position,start_position,bold);
      endif;
    endif;

  else          ! search for matching close character
    open_char := target;
    close_char  := substr(evedt_matchable_close,open_type,1);

    start_position := mark (none);
    left_matches := 1;
    move_horizontal (1);
    match_chars := open_char + close_char;
    all_chars := open_char + close_char; ! + quote_chars;
    match_pattern := any (all_chars);

    loop        ! looking for close char so search forward
      match_position := search (match_pattern, forward);
      exitif match_position = 0;
      position (match_position);

      if current_character = substr (match_chars, 1, 1) then
        left_matches := left_matches + 1;
        move_horizontal (1);
      else
        if current_character = substr (match_chars, 2, 1) then
          left_matches := left_matches - 1;
        move_horizontal (1);
        endif;
      endif;

      exitif left_matches = 0;
    endloop;

    if left_matches = 0 then
      move_horizontal (-1);  
      found_position := mark(none);
      match_range := create_range(start_position,found_position,bold);
    endif;
  endif;

  position (start_position);

endprocedure;

!*----------------------------------------------------------------------------*!
! This procedure is passed the pair of matching characters and a pair of
! quote characters.

procedure evedt_match (match_chars, quote_chars)

local this_position,            ! Marker - current cursor position
      right_matches,            ! Integer - number of opens to close
      all_chars,                ! String - match_chars + quote_chars
      match_pattern,            ! Pattern - any (all_chars)
      match_position,           ! Marker - current position during searches
      new_string,
      rd_str,
      at_line,
      low_line,
      this_quote;               ! String - current quote character

  on_error
    [TPU$_CONTROLC]:
	eve$learn_abort;
	abort;
    [OTHERWISE]:
	abort;
  endon_error;

    if length (match_chars) <> 2 then
        message ("Must have 2 characters to match");
        return;
    endif;

    at_line := eve_get_line;

    copy_text (substr (match_chars, 2, 1));
    this_position := mark (none);
    right_matches := 1;
    move_horizontal (-1);
    all_chars := match_chars + quote_chars;
    match_pattern := any (all_chars);
    loop
        ! looking for open char so search backwards
        match_position := search (match_pattern, reverse);
        exitif match_position = 0;
        position (match_position);
        if index (quote_chars, current_character) > 0 then
            this_quote := current_character;
            move_horizontal (-1);
            match_position := search (this_quote, reverse);
            exitif match_position = 0;
            position (match_position);
        else
            if current_character = substr (match_chars, 1, 1) then
                right_matches := right_matches - 1;
            else
                right_matches := right_matches + 1;
           endif;
        endif;
        exitif right_matches = 0;
    endloop;

    if right_matches = 0 then
        low_line := eve_get_line;
        evedt_display_line;
        rd_str := "Match found on line " + str(low_line) +
                  " You are on line " + str(at_line);
        new_string := read_line (rd_str);
    else
        !message ("No matching character found");
    endif;

    position (this_position);

endprocedure;

!*----------------------------------------------------------------------------*!

! Internal routine for evedt_match
! Display current line in message window, with current position highlighted

procedure evedt_display_line          ! Display the matching line

local this_position,            ! Marker - current cursor position
      this_line,                ! String - current line
      start_of_line,            ! Marker - Start of current line
      this_offset;              ! Integer - offset of this_position

    this_position := mark (blink);
    this_offset := current_offset;
    move_horizontal (- current_offset);
    start_of_line := mark (none);
    move_horizontal (length (current_line));
    this_line := create_range (start_of_line, mark (none), none);
    message (this_line);
    position (end_of (message_buffer));
    move_vertical (-1);
    move_horizontal (this_offset);
    evedt_this_position := mark (blink);
    position (this_position);

endprocedure;

!*----------------------------------------------------------------------------*!

procedure eve_set_matching(the_arg)     ! Turn on electric open parens

LOCAL   the_key,
        the_keys,
        ptr;

    the_keys := the_arg;

    if (the_keys = "") then
        the_keys := read_line("Match what characters: ");
    endif;
    ptr := 1;
    loop
        exitif (ptr > length(the_keys));
        the_key := substr(the_keys, ptr, 1);
        if (index(evedt_matchable_open, the_key) <> 0) then
            define_key("evedt_insert_matched", key_name(the_key), " typing");
        else
            message('"' + the_key + '" is not matchable');
            return;
        endif;
        ptr := ptr + 1;
    endloop;

endprocedure;

!*----------------------------------------------------------------------------*!

procedure eve_set_nomatching(the_arg)           ! Turn off electric open parens

LOCAL   the_key,
        the_keys,
        ptr;

    the_keys := the_arg;

    if (the_keys = "") then
        the_keys := read_line("Remove matching for what charcters: ");
    endif;
    ptr := 1;
    loop
        exitif (ptr > length(the_keys));
        the_key := substr(the_keys, ptr, 1);
        if (index(evedt_matchable_open, the_key) <> 0) then
            undefine_key(key_name(the_key));
        else
            if (index(evedt_matchable_close, the_key) = 0) then
                message('"' + the_key + '" is not matchable');
                return;
            endif;
        endif;
        ptr := ptr + 1;
    endloop;

endprocedure;

!*----------------------------------------------------------------------------*!

procedure evedt_insert_matched                ! Insert the two caharcters

LOCAL   the_key,
        which;

    the_key := ascii(last_key);
    which := index(evedt_matchable_open, the_key);
    if (which <> 0) then
        evedt_insert_text(the_key);
        evedt_insert_text(substr(evedt_matchable_close, which, 1));
        move_horizontal(-1);
    else
        message("That key isn't matchable.");
        return;
    endif;

endprocedure

!*----------------------------------------------------------------------------*!

! This procedure will find the match to the current begin / end string.

procedure eve_find_be_match

local start_position,           ! Marker - current cursor position
      found_position,           ! Marker - position of found matching char
      betin_matches,            ! Integer - number of opens to close
      end_matches,              ! Integer - number of closes to open
      match_pattern,            ! Pattern - any (all_chars)
      match_position,           ! Marker - current position during searches
      target,
      begin_type,
      end_type,
      org_direction;

   on_error
    [TPU$_CONTROLC]:
        position (start_position);
	eve$learn_abort;
	abort;
    [TPU$_STRNOTFOUND]:
        message ("No matching string found.");
        position (start_position);
	abort;
    [OTHERWISE]:
	abort;
  endon_error;

  if current_character = ' ' then
    message ( "> < is not a begin / end pattern.");
    return;
  endif;

  org_direction := current_direction;

  target := tdd_get_word(eve$read_word_separators);
  edit(target,collapse,lower);

  begin_type := 'begin';
  end_type := 'end';
  end_semi := 'end;';
  match_pattern := begin_type | end_type;

  if target <> begin_type then

    if (target <> end_type) and
       (target <> end_semi) then
      message ( ">" + target + "<" + " is not a begin / end pattern.");
    else  ! search for matching begin

      eve$end_of_word;
      move_horizontal(-1);   
      start_position := mark (none);
      end_matches := 1;

      ! move to the beginning of the word

      if not eve$at_start_of_word then
        eve$start_of_word;
      endif;

      ! set direction reverse

      if current_direction = forward then
        eve_change_direction;
      endif;

      eve_move_by_word;

      loop        ! looking for begin string so search backwards
        match_position := search (match_pattern, reverse);
        exitif match_position = 0;
        position (match_position);

        target := tdd_get_word(eve$read_word_separators);
        edit(target,collapse,lower);

        if target = begin_type then
          end_matches := end_matches - 1;
          move_horizontal (-1);
        else
          if (target = end_type) or
             (target = end_semi) then
            end_matches := end_matches + 1;
            move_horizontal (-1);
          endif;
        endif;

        exitif end_matches = 0;
      endloop;

      if end_matches = 0 then
        move_horizontal (1);
        found_position := mark(none);
        match_range := create_range(found_position,start_position,bold);
      endif;
    endif;
  else          ! search for matching end string
    if not eve$at_start_of_word then
      eve$start_of_word;
    endif;

    start_position := mark (none);
    begin_matches := 1;

    if current_direction = reverse then
      eve_change_direction;
    endif;

    ! move to the beginning of the next word

    eve_move_by_word;

    loop        ! looking for begin string so search forwards
      match_position := search (match_pattern, forward);
      exitif match_position = 0;
      position (match_position);

      target := tdd_get_word(eve$read_word_separators);
      edit(target,collapse,lower);

      if (target = end_type) or
         (target = end_semi) then
        begin_matches := begin_matches - 1;
        move_horizontal (1);
      else
        if target = begin_type then
          begin_matches := begin_matches + 1;
          move_horizontal (1);
        else
          move_horizontal (1);
        endif;
      endif;

      exitif begin_matches = 0;
    endloop;

    if begin_matches = 0 then
      move_horizontal (1);
      found_position := mark(none);
      match_range := create_range(found_position,start_position,bold);
    endif;
  endif;

  position (start_position);

  if current_direction <> org_direction then
    eve_change_direction;
  endif;

endprocedure;

!*----------------------------------------------------------------------------*!

