###########################################################
# Name:    hl_c.tcl
# Author:  Alex Plotnikov  (aplsimple@gmail.com)
# Date:    06/16/2021
# Brief:   Handles highlighting C code.
# License: MIT.
###########################################################

# ______________________ Common data ____________________ #

namespace eval ::hl_c {

  namespace eval my {

    variable data;  array set data [list]

    # C reserved words
    set data(PROC_C) [lsort [list goto return]]
    set data(CMD_C1) [list \
    auto break case char const continue default do double else enum extern \
    float for if inline int long register restrict short signed sizeof \
    static struct switch typedef union unsigned void volatile while \
    ]
    # other C token
    set data(CMD_C2) [list \
    ifdef endif \
    ]
    set data(CMD_C) [lsort [concat $data(CMD_C1) $data(CMD_C2)]]

    # C++ reserved words
    set data(CMD_CPP1) [list \
    and and_eq asm bitand bitor bool catch class compl const_cast delete \
    dynamic_cast explicit false friend inline  mutable namespace new \
    not not_eq operator or or_eq private public protected \
    reinterpret_cast static_cast template this throw true try typeid \
    typename using virtual wchar_t xor xor_eq \
    ]

    # C++ predefined identifiers
    set data(CMD_CPP2) [list \
    cin cout endl include INT_MAX INT_MIN iomanip iostream main MAX_RAND \
    npos NULL std string FALSE TRUE \
    ]

    # C++ united
    set data(CMD_CPP) [lsort [concat $data(CMD_CPP1) $data(CMD_CPP2)]]

    # regexp for key words and punctuation
    set data(RE0) {\w+}
    set data(RE1) {[<>(){};=!%&^*+-.,\|:/\\#]+}

    # characters of multiline comment, string and char and their counterparts
    set data(PAIR1) [list /* \" ' //]
    set data(PAIR2) [list */ \" ']
    set data(PAIR_COMMENT) 0  ;# indices for multiline comment/ string/ char
    set data(PAIR_STRING) 1
    set data(PAIR_CHAR) 2
    set data(PAIR_COMMENT1) 3
  }
}

# _________________________ STATIC highlighting _________________________ #

proc ::hl_c::my::HighlightCmd {txt line ln pri i} {
  # Highlights C commands.
  #   txt - text widget's path
  #   line - line to be highlighted
  #   ln - line number
  #   pri - column number to highlighted from
  #   i - current position in 'line'

  variable data
  $txt tag add tagSTD "$ln.$pri" "$ln.$i"
  set st [string range $line $pri $i]
  set lcom [regexp -inline -all -indices $data(RE0) $st]
  foreach lc $lcom {
    lassign $lc i1 i2
    set word [string range $st $i1 $i2]
    if {$word in $data(CMD_C)} {
      $txt tag add tagCOM "$ln.$pri +$i1 char" "$ln.$pri +[incr i2] char"
    } elseif {$word in $data(PROC_C)} {
      $txt tag add tagPROC "$ln.$pri +$i1 char" "$ln.$pri +[incr i2] char"
    } elseif {$word in $data(CMD_CPP)} {
      $txt tag add tagCOMTK "$ln.$pri +$i1 char" "$ln.$pri +[incr i2] char"
    } elseif {$word in $data(KEYWORDS,$txt)} {
      $txt tag add tagOPT "$ln.$pri +$i1 char" "$ln.$pri +[incr i2] char"
    }
  }
  set lcom [regexp -inline -all -indices $data(RE1) $st]
  foreach lc $lcom {
    lassign $lc i1 i2
    $txt tag add tagVAR "$ln.$pri +$i1 char" "$ln.$pri +[incr i2] char"
  }
}
#_______________________

proc ::hl_c::my::HighlightLine {txt ln currQtd} {
  # Highlightes a line in text.
  #   txt - text widget's path
  #   ln - line's number
  #   currQtd - 0, 1 or 2 referring to comment/string/char
  # Returns currQtd for the end of the line.

  variable data
  set line [$txt get $ln.0 $ln.end]
  set i1 [set i0 0]
  set i2 [set llen [string length $line]]
  while {$i1<$llen} {
    if {$currQtd>-1} {
      # if quoted then there follows a multiline comment or a string/char
      # => find its pair
      if {$currQtd==$data(PAIR_COMMENT)} {
        set tag tagCMN  ;# comment
      } else {
        set tag tagSTR  ;# string/char
      }
      set ch [lindex $data(PAIR2) $currQtd]
      set i2 [string first $ch $line $i1]
      while {$i2!=-1} {
        if {[::hl_tcl::my::NotEscaped $line $i2]} break
        set i2 [string first $ch $line $i2+1]
      }
      if {$i2==-1} {
        # a pair not found - the comment/string/char is not ended
        if {$currQtd!=$data(PAIR_COMMENT) && \
        ($currQtd==$data(PAIR_CHAR) || [string index $line end] ne "\\")} {
          set currQtd -1  ;# char or string not ended properly
        }
        $txt tag add $tag $ln.$i0 $ln.end
        return $currQtd
      }
      # the pair found - highlight an appropriate part of the line
      set lp [string length [lindex $data(PAIR2) $currQtd]]
      $txt tag add $tag $ln.[incr i1 -$lp] $ln.[incr i2 $lp]
      set i1 $i2
      set currQtd -1
      continue
    }
    # not quoted - find a nearest "quote" (comment/string/char)
    set i0 $llen
    set i 0
    foreach par $data(PAIR1) {
      if {[set p [string first $par $line $i1]]>-1 && $p<$i0 && [::hl_tcl::my::NotEscaped $line $p]} {
        set i0 $p
        set ip $i
      }
      incr i
    }
    if {$i0<$llen} {
      HighlightCmd $txt $line $ln $i1 [expr {$i0-1}]
      # a comment/string/char was found
      if {$ip==$data(PAIR_COMMENT1)} {
        if {[regexp -start $i0 {\s*//\s*(!|TODO)} $line]} {
          $txt tag add tagCMN2 $ln.$i0 $ln.end
        } else {
          $txt tag add tagCMN1 $ln.$i0 $ln.end
        }
        return -1  ;# it was a one-line comment
      }
      set currQtd $ip
      set lp [string length [lindex $data(PAIR2) $currQtd]]
      set i1 [expr {$i0+$lp}]
      if {$i1>=($llen-1)} {
        if {$currQtd==$data(PAIR_COMMENT)} {
          set tag tagCMN  ;# comment
        } else {
          set tag tagSTR  ;# string/char
        }
        $txt tag add $tag $ln.$i0 $ln.end
      }
      continue
    }
    HighlightCmd $txt $line $ln $i1 [expr {$llen-1}]
    set currQtd -1
    break
  }
  return $currQtd
}
#_______________________

proc ::hl_c::my::HighlightAll {txt} {
  # Highlights all of a text.
  #   txt - text widget's path
  # Makes a coroutine from this.
  # See also: CoroHighlightAll

  # let them work one by one:
  set coroNo [expr {[incr ::hl_c::my::data(CORALL)] % 10000000}]
  coroutine co_HlAll$coroNo ::hl_c::my::CoroHighlightAll $txt
}
#_______________________

proc ::hl_c::my::CoroHighlightAll {txt} {
  # Highlights all of a text as a coroutine.
  #   txt - text widget's path
  # See also: HighlightAll

  variable data
  catch {  ;# $txt may be destroyed, so catch this
    if {!$data(PLAINTEXT,$txt)} {
      set tlen [lindex [split [$txt index end] .] 0]
      RemoveTags $txt 1.0 end
      set maxl [expr {min($::hl_c::my::data(SEEN,$txt),$tlen)}]
      set maxl [expr {min($::hl_c::my::data(SEEN,$txt),$tlen)}]
      set currQtd -1
      for {set ln [set lnseen 0]} {$ln<=$tlen} {} {
        set currQtd [HighlightLine $txt $ln $currQtd]
        incr ln
        if {[incr lnseen]>$::hl_c::my::data(SEEN,$txt)} {
          set lnseen 0
          after idle after 1 [info coroutine]
          yield
        }
      }
    }
  }
  set ::hl_c::my::data(REG_TXT,$txt) {1}
  return
}

# _________________________ DYNAMIC highlighting ________________________ #

proc ::hl_c::my::RemoveTags {txt from to} {
  # Removes tags in text.
  #   txt - text widget's path
  #   from - starting index
  #   to - ending index

  foreach tag {tagCOM tagCOMTK tagSTR tagVAR tagCMN tagCMN1 tagCMN2 tagPROC tagOPT} {
    $txt tag remove $tag $from $to
  }
}
#_______________________

proc ::hl_c::my::CountQSH {txt ln} {
  # Counts quotes, slashes, comments in a line
  #   txt - text widget's path
  #   ln - line's index

  set ln [expr {int($ln)}]
  set st [$txt get $ln.0 $ln.end]
  set quotes 0
  foreach spch {"\"" '} {
    incr quotes [::hl_tcl::my::CountChar $st $spch]
  }
  set slashes [::hl_tcl::my::CountChar $st "\\"]
  set comments 0
  foreach spch {/ *} {
    incr comments [::hl_tcl::my::CountChar $st $spch]
  }
  list $quotes $slashes $comments
}
#_______________________

proc ::hl_c::my::MemPos1 {txt {donorm yes} {K ""} {s ""}} {
  # Checks and sets the cursor's width, depending on its position.
  #   txt - text widget's path
  #   donorm - if yes, forces "normal" cursor
  #   K - key (%K of bind)
  #   s - state (%s of bind)
  # This fixes an issue with text cursor: less width at 0th column.

  variable data
  if {$K eq "Home" && [string is digit -strict $s] && \
  [expr {$s & 4}]==0 && [expr {$s & 1}]==0} {
    # Ctrl-Home & Shift-Home are passed
    set p1 [$txt index insert]
    set line [$txt get "$p1 linestart" "$p1 lineend"]
    set p [expr {[string length $line]-[string length [string trimleft $line]]}]
    set p2 [expr {int($p1)}].$p
    if {$p && $p2 ne $p1} {
      after idle "::tk::TextSetCursor $txt $p2"
      return
    }
  }
  if {$data(INSERTWIDTH,$txt)==1} {
    if {[$txt cget -insertwidth]!=1} {$txt configure -insertwidth 1}
    return 0
  }
  set insLC [$txt index insert]
  lassign [split $insLC .] L C
  if {$data(_INSPOS_,$txt) eq ""} {
    set L2 [set C2 0]
  } else {
    lassign [split $data(_INSPOS_,$txt) .] L2 C2
  }
  if {$L!=$L2 || $C==0 || $C2==0} {
    if {$C || $donorm} {
      $txt configure -insertwidth $data(INSERTWIDTH,$txt)
    } else {
      $txt configure -insertwidth [expr {$data(INSERTWIDTH,$txt)*2-1}]
    }
  }
  return $insLC
}

proc ::hl_c::my::MemPos {txt {doit no}} {
  # Remembers the state of current line.
  #   txt - text widget's path
  #   doit - argument for ShowCurrentLine
  # See also: ShowCurrentLine

  variable data
  set data(_INSPOS_,$txt) [MemPos1 $txt no]
  set ln [ShowCurrentLine $txt $doit]
  set data(CURPOS,$txt) $ln
  set data(CUR_LEN,$txt) [$txt index {end -1 char}]
  lassign [CountQSH $txt $ln] \
    data(CNT_QUOTE,$txt) data(CNT_SLASH,$txt) data(CNT_COMMENT,$txt)
  if {[$txt tag ranges tagBRACKET] ne {}}    {$txt tag remove tagBRACKET 1.0 end}
  if {[$txt tag ranges tagBRACKETERR] ne {}} {$txt tag remove tagBRACKETERR 1.0 end}
  if {[set cmd $data(CMDPOS,$txt)] ne {}} {
    # run a command after changing position (with the state as arguments)
    append cmd " $txt $data(CUR_LEN,$txt) $ln $data(CNT_QUOTE,$txt) \
      $data(CNT_SLASH,$txt) $data(CNT_COMMENT,$txt)"
    catch {after cancel $data(CMDATFER,$txt)}
    set data(CMDATFER,$txt) [after idle $cmd]
  }
}
#_______________________

proc ::hl_c::my::Modified {txt oper pos1 args} {
  # Handles modifications of text.
  #   txt - text widget's path
  # Makes a coroutine from this.
  # See also: CoroModified

  variable data
  set ar2 [lindex $args 0]
  set posins [$txt index insert]
  if {[catch {set pos1 [set pos2 [$txt index $pos1]]}]} {
    set pos1 [set pos2 $posins]
  }
  switch $oper {
    insert {
      set pos2 [expr {$pos1 + [llength [split $ar2 \n]]}]
    }
    delete {
      if {$ar2 eq {} || [catch {set pos2 [$txt index $ar2]}]} {
        set pos2 $posins
      }
    }
  }
  after idle "::hl_c::my::CoroRun $txt $pos1 $pos2 $args"
}
#_______________________

proc ::hl_c::my::CoroRun {txt pos1 pos2 args} {

  variable data
  if {![info exist data(REG_TXT,$txt)] || $data(REG_TXT,$txt) eq {} || \
  ![info exist data(CUR_LEN,$txt)]} {
    return  ;# skip changes till the highlighting done
  }
  # let them work one by one
  set i1 [expr {int($pos1)}]
  set i2 [expr {int($pos2)}]
  set coroNo [expr {[incr ::hl_c::my::data(CORMOD)] % 10000000}]
  coroutine CoModified$coroNo ::hl_c::my::CoroModified $txt $i1 $i2 {*}$args
}
#_______________________

proc ::hl_c::my::CoroModified {txt {i1 -1} {i2 -1} args} {
  # Handles modifications of text.
  #   txt - text widget's path
  # See also: Modified

  catch {
    variable data
    # current line:
    set ln [expr {int([$txt index insert])}]
    # ending line:
    set endl [expr {int([$txt index {end -1 char}])}]
    # range of change:
    if {$i1!=-1} {
      set dl [expr {abs($i2-$i1)}]
      set ln $i1
    } else {
      set dl [expr {abs(int($data(CUR_LEN,$txt)) - $endl)}]
    }
    # begin and end of changes:
    set ln1 [set lno1 [expr {max(($ln-$dl),1)}]]
    set ln2 [set lno2 [expr {min(($ln+$dl),$endl)}]]
    lassign [CountQSH $txt $ln] cntq cnts ccmnt
    # flag "highlight to the end":
    set bf1 [expr {abs($ln-int($data(CURPOS,$txt)))>1 || $dl>1 \
    || $cntq!=$data(CNT_QUOTE,$txt) \
    || $ccmnt!=$data(CNT_COMMENT,$txt)}]
    set bf2 [expr {$cnts!=$data(CNT_SLASH,$txt)}]
    if {$bf1 && !$data(MULTILINE,$txt) || $bf2} {
      set lnt1 $ln
      set lnt2 [expr {$ln+1}]
      while {$ln2<$endl && $lnt1<$endl && $lnt2<=$endl && ( \
      [$txt get "$lnt1.end -1 char" $lnt1.end] in {\\ \"} ||
      [$txt get "$lnt2.end -1 char" $lnt2.end] in {\\ \"}) || $bf2} {
        incr lnt1 ;# next lines be handled too, if ended with "\\"
        incr lnt2
        incr ln2
        set bf2 0
      }
    }
    set tSTR [$txt tag ranges tagSTR]
    set tCMN [$txt tag ranges tagCMN]
    if {$ln1==1} {
      set currQtd -1
    } else {
      set currQtd [LineState $txt $tSTR $tCMN "$ln1.0 -1 chars"]
    }
    if {!$data(PLAINTEXT,$txt)} {
      set lnseen 0
      $txt tag add tagSTD $ln1.0 $ln2.end
      while {$ln1<=$ln2} {
        if {$ln1==$ln2} {
          set bf2 [LineState $txt $tSTR $tCMN "$ln1.end +1 chars"]
        }
        RemoveTags $txt $ln1.0 $ln1.end
        set currQtd [HighlightLine $txt $ln1 $currQtd]
        if {$ln1==$ln2 && ($bf1 || $bf2!=$currQtd) && $data(MULTILINE,$txt)} {
          set ln2 $endl  ;# run to the end
        }
        if {[incr lnseen]>$::hl_c::my::data(SEEN,$txt)} {
          set lnseen 0
          catch {after cancel $data(COROATFER,$txt)}
          set data(COROATFER,$txt) [after idle after 1 [info coroutine]]
          yield
        }
        incr ln1
      }
    }
    if {[set cmd $data(CMD,$txt)] ne {}} {
      # run a command after changes done (its arguments are txt, ln1, ln2)
      append cmd " $txt $lno1 $lno2 $args"
      after idle $cmd
    }
    MemPos $txt
    return
  }
}
#_______________________

proc ::hl_c::my::LineState {txt tSTR tCMN l1} {
  # Gets an initial state of line.
  #   txt - text widget's path
  #   tSTR - ranges of string tags
  #   tCMN - ranges of comment tags
  #   l1 - the line's index
  # Returns: a flag of 'quoted' line or -1.

  set i1 [$txt index $l1]
  if {[set prev [string first -1 $l1]]>-1} {
    set i1 [$txt index "$i1 -1 chars"]
  }
  if {[::hl_tcl::my::SearchTag $tCMN [$txt index "$i1 -1 chars"]]!=-1} {
    if {[$txt get "$i1 -1 chars" "$i1 +1 chars"] eq {*/}} {
      return -1
    }
    return 0
  }
  set ch [$txt get "$i1" "$i1 +1 chars"]
  if {[::hl_tcl::my::SearchTag $tSTR [$txt index "$i1 -1 chars"]]!=-1} {
    if {$ch eq "\\"} {return 1}
  }
  return -1
}
#_______________________

proc ::hl_c::my::ShowCurrentLine {txt {doit no}} {
  # Shows the current line.
  #   txt - text widget's path
  #   doit - if yes, forces updating current line's background

  variable data
  set pos [$txt index insert]
  lassign [split $pos .] ln cn
  if {[::hl_tcl::my::IsCurline $txt]} {
    if {$doit || ![info exists data(CURPOS,$txt)] || int($data(CURPOS,$txt))!=$ln || $cn<2} {
      $txt tag remove tagCURLINE 1.0 end
      $txt tag add tagCURLINE [list $pos linestart] [list $pos lineend]+1displayindices
    }
  }
  return $pos
}

# _________________________ INTERFACE procedures ________________________ #

proc ::hl_c::hl_readonly {txt {ro -1} {com2 ""}} {
  # Makes the text widget be readonly or gets its 'read-only' state.
  #   txt - text widget's path
  #   ro - flag "the text widget is readonly"
  #   com2 - command to be called at viewing and after changes
  # If 'ro' argument is omitted, returns the widget's 'read-only' state.

  if {$ro==-1} {
    return [expr {[info exists ::hl_c::my::data(READONLY,$txt)] && $::hl_c::my::data(READONLY,$txt)}]
  }
  set ::hl_c::my::data(READONLY,$txt) $ro
  if {$com2 ne {}} {set ::hl_c::my::data(CMD,$txt) $com2}
  set newcom "::$txt.internal"
  if {[info commands $newcom] eq ""} {rename $txt $newcom}
  set com "[namespace current]::my::Modified $txt"
  #if {$com2 ne ""} {append com " ; $com2"}
  if {$ro} {proc ::$txt {args} "
    if {!\[winfo exists $txt\]} {return 0}
    switch -exact -- \[lindex \$args 0\] \{
      insert \{$com2\}
      delete \{$com2\}
      replace \{$com2\}
      default \{ return \[eval $newcom \$args\] \}
    \}"
  } else {proc ::$txt {args} "
    if {!\[winfo exists $txt\]} {return 0}
    switch -exact -- \[lindex \$args 0\] \{
      delete \{$com {*}\$args\}
      insert \{$com {*}\$args\}
      replace \{$com {*}\$args\}
    \}
    set _res_ \[eval $newcom \$args\]
    return \$_res_"
  }
}
#_______________________

proc ::hl_c::hl_init {txt args} {
  # Initializes highlighting.
  #   txt - text widget's path
  #   args - dict of options
  # The 'args' options include:
  #   -- - means that only args' options will be initialized (defaults skipped)
  #   -dark - flag "the text widget has dark background"
  #   -readonly - flag "read-only"
  #   -optRE - flag "use of RE to highlight options"
  #   -multiline - flag "allowed multi-line strings"
  #   -cmd - command to watch editing/viewing
  #   -cmdpos - command to watch cursor positioning
  #   -colors - list of colors: clrCOM, clrCOMTK, clrSTR, clrVAR, clrCMN, clrPROC
  #   -font - attributes of font
  #   -seen - lines seen at start
  # This procedure has to be called before writing a text in the text widget.

  if {[set setonly [expr {[lindex $args 0] eq {--}}]]} {
    set args [lrange $args 1 end]
  }
  ::hl_tcl::iscurline $txt 1
  set ::hl_c::my::data(REG_TXT,$txt) {}  ;# disables Modified at changing the text
  set ::hl_c::my::data(KEYWORDS,$txt) {}
  # get default options from text's ones
  set defopts [list -insertwidth]
  foreach defopt $defopts {
    set opt [::hl_tcl::OptName $txt $defopt]
    if {![info exists ::hl_c::my::data($opt)]} {
      set ::hl_c::my::data($opt,DEFAULT) [$txt cget $defopt]
    }
  }
  foreach {opt val} {-dark 0 -readonly 0 -cmd {} -cmdpos {} -optRE 1 \
  -multiline 1 -seen 500 -plaintext no -insertwidth {} -keywords {}} {
    if {[dict exists $args $opt]} {
      set val [dict get $args $opt]
    } elseif {$setonly} {
      continue  ;# only those set in args are taken into account
    }
    set ::hl_c::my::data([::hl_tcl::OptName $txt $opt]) $val
  }
  # reget default options from text's ones
  foreach defopt $defopts {
    set opt [::hl_tcl::OptName $txt $defopt]
    if {[info exists ::hl_c::my::data($opt)] && $::hl_c::my::data($opt) eq {}} {
      set ::hl_c::my::data($opt) $::hl_c::my::data($opt,DEFAULT)
    }
  }
  set ::hl_c::my::data(KEYWORDS,$txt) [lsort $::hl_c::my::data(KEYWORDS,$txt)]
  if {[dict exists $args -colors]} {
    set ::hl_c::my::data(COLORS,$txt) [dict get $args -colors]
    set ::hl_c::my::data(SETCOLORS,$txt) 1
  } else {
    if {![info exists ::hl_c::my::data(COLORS,$txt)]}  {
      set clrCURL {}
      catch {set clrCURL [lindex [::apave::obj csGet] 16]}
      if {$::hl_c::my::data(DARK,$txt)} {
        if {$clrCURL eq {}} {set clrCURL #29383c}
        set ::hl_c::my::data(COLORS,$txt) [list {*}[hl_colors $txt] $clrCURL]
      } else {
        if {$clrCURL eq {}} {set clrCURL #efe0cd}
        set ::hl_c::my::data(COLORS,$txt) [list {*}[hl_colors $txt] $clrCURL]
      }
    }
  }
  if {!$setonly} {
    if {[dict exists $args -font]} {
      set ::hl_c::my::data(FONT,$txt) [dict get $args -font]
    } else {
      set ::hl_c::my::data(FONT,$txt) [font actual TkFixedFont]
    }
  }
  if {!$setonly || [dict exists $args -readonly]} {
    hl_readonly $txt $::hl_c::my::data(READONLY,$txt)
  }
  if {[string first ::hl_c:: [bind $txt]]<0} {
    ::hl_tcl::my::BindToEvent $txt <FocusIn> ::hl_c::my::ShowCurrentLine $txt
  }
  set ::hl_c::my::data(_INSPOS_,$txt) {}
  my::MemPos $txt
}
#_______________________

proc ::hl_c::hl_text {txt} {
  # Highlights C code of a text widget.
  #   txt - text widget's path

  set font0 $::hl_c::my::data(FONT,$txt)
  set font1 [set font2 $font0]
  $txt tag configure tagSTD -font "$font0"
  $txt tag add tagSTD 1.0 end
  dict set font1 -weight bold
  dict set font2 -slant italic
  lassign $::hl_c::my::data(COLORS,$txt) \
    clrCOM clrCOMTK clrSTR clrVAR clrCMN clrPROC clrOPT clrBRA clrCURL clrCMN2
  $txt tag configure tagCOM -font "$font1" -foreground $clrCOM
  $txt tag configure tagCOMTK -font "$font1" -foreground $clrCOMTK
  $txt tag configure tagSTR -font "$font0" -foreground $clrSTR
  $txt tag configure tagVAR -font "$font0" -foreground $clrVAR
  $txt tag configure tagCMN -font "$font2" -foreground $clrCMN
  $txt tag configure tagCMN1 -font "$font2" -foreground $clrCMN
  $txt tag configure tagCMN2 -font "$font2" -foreground $clrCMN2 ;#red
  $txt tag configure tagPROC -font "$font1" -foreground $clrPROC
  $txt tag configure tagOPT -font "$font1" -foreground $clrOPT
  $txt tag configure tagBRACKET -font "$font0" -foreground $clrBRA
  $txt tag configure tagBRACKETERR -font "$font0" -foreground white -background red
  $txt tag configure tagCURLINE -background $clrCURL
  $txt tag raise sel
  $txt tag raise tagBRACKETERR
  catch {$txt tag raise hilited;  $txt tag raise hilited2} ;# for apave package
  my::HighlightAll $txt
  ::hl_tcl::my::BindToEvent $txt <FocusIn> ::hl_c::my::MemPos $txt
  ::hl_tcl::my::BindToEvent $txt <KeyPress> ::hl_c::my::MemPos1 $txt yes %K %s
  ::hl_tcl::my::BindToEvent $txt <KeyRelease> ::hl_c::my::MemPos $txt
  ::hl_tcl::my::BindToEvent $txt <ButtonRelease-1> ::hl_c::my::MemPos $txt
  foreach ev {Enter KeyRelease ButtonRelease-1} {
    ::hl_tcl::my::BindToEvent $txt <$ev> ::hl_tcl::my::HighlightBrackets $txt
  }
  set ro $::hl_c::my::data(READONLY,$txt)
  set com2 $::hl_c::my::data(CMD,$txt)
  set txtattrs [list $txt $ro $com2]
  if {![info exists ::hl_c::my::data(LIST_TXT)] || \
  [set i [lsearch -index 0 -exact $::hl_c::my::data(LIST_TXT) $txt]]==-1} {
    lappend ::hl_c::my::data(LIST_TXT) $txtattrs
  } else {
    set ::hl_c::my::data(LIST_TXT) [lreplace $::hl_c::my::data(LIST_TXT) $i $i $txtattrs]
  }
  hl_readonly $txt $ro $com2
}
#_______________________

proc ::hl_c::hl_all {args} {
  # Updates ("rehighlights") all highlighted and existing text widgets.
  #   args - dict of options
  # See also: hl_init

  if {[info exists ::hl_c::my::data(LIST_TXT)]} {
    foreach wattrs $::hl_c::my::data(LIST_TXT) {
      lassign $wattrs txt ro com2
      if {[winfo exists $txt]} {
        if {![info exists ::hl_c::my::data(SETCOLORS,$txt)]} {
          unset ::hl_c::my::data(COLORS,$txt) ;# colors defined by DARK
        }
        # args (if set) override the appropriate settings for $txt
        hl_init $txt -- {*}$args
        hl_text $txt
      }
    }
  }
}
#_______________________

proc ::hl_c::hl_colors {txt {dark ""}} {
  # Gets the main colors for highlighting (except for "curr.line").
  #   txt - text widget's path or {} or an index of default colors
  #   dark - flag "dark scheme"
  # Returns a list of colors for COM COMTK STR VAR CMN PROC OPT BRAC \
   or, if the colors aren't initialized, "standard" colors.

  if {[info exists ::hl_c::my::data(COLORS,$txt)]}  {
    return $::hl_c::my::data(COLORS,$txt)
  }
  if {$dark eq {}} {set dark $::hl_c::my::data(DARK,$txt)}
  if {![string is integer -strict $txt] || $txt<0 || $txt>3} {set txt 0}
  if {$dark} {set dark 1} {set dark 0}
  set res [lindex $::hl_tcl::my::data(SYNTAXCOLORS,$txt) $dark]
  # user keywords' color
  set res [lreplace $res 6 6 [lindex $res 1]]
  return $res
}
#_______________________

proc ::hl_c::hl_line {txt} {
  # Updates a current line's highlighting.
  #   txt - text's path

  if {!$::hl_c::my::data(PLAINTEXT,$txt)} {
    set ln0 [expr {int([$txt index insert])}]
    set ln2 [expr {int([$txt index end])}]
    set ln1 [expr {max (1,$ln0-1)}]
    set ln2 [expr {min ($ln2,$ln0+1)}]
    # update lines: previous, current, next
    after idle "::hl_c::my::CoroRun $txt $ln1 $ln2"
  }
  ::hl_c::my::MemPos $txt yes
  $txt configure -insertwidth $::hl_c::my::data(INSERTWIDTH,$txt)
}
#_______________________

proc ::hl_c::isdone {txt} {
  # Checks if the highlighting of the text is done.
  #   txt - text's path

  variable my::data
  return [expr {[info exist my::data(REG_TXT,$txt)] && $my::data(REG_TXT,$txt) ne {}}]
}
#_______________________

proc ::hl_c::clearup {txt} {
  # Clears data related to text (esp. at deleting it).
  #   txt - text's path

  variable my::data
  foreach key [array names my::data *,$txt] {
    unset my::data($key)
  }
  foreach i [lsearch -all -exact -index 0 $my::data(LIST_TXT) $txt] {
    set my::data(LIST_TXT) [lreplace $my::data(LIST_TXT) $i $i]
  }
}
#_______________________

proc ::hl_c::cget {txt opt} {
  # Gets a highlighting option's value.
  #   txt - text's path
  #   opt - option's name

  variable my::data
  set opt [string toupper [string trimleft $opt -]]
  if {[info exists my::data($opt,$txt)]} {
    return $my::data($opt,$txt)
  }
  return {}
}
#_______________________

proc ::hl_c::configure {txt opt val} {
  # Sets a highlighting option's value.
  #   txt - text's path
  #   opt - option's name
  #   val - option's value

  variable my::data
  set opt [string toupper [string trimleft $opt -]]
  set my::data($opt,$txt) $val
}

# _________________________________ EOF _________________________________ #
