**************
* Программа..:  xDebug - Среда программирования (редактор, отладка...)
* Имя файла..:  xDebug.Prg
* Автор......:  Колуж Андрей
* Copyright (c) 1993-2005 "Компания БЭСТ". Все права сохраняются.
**************

//#include "hdll.ch"
//#include "set.ch"
//#include "gtinfo.ch"

#include "inkey.ch"
#include "memoedit.ch"
#include "setcurs.ch"
#include "s_public.ch"


#define DEBUG

/* Структура для хранения информации о редакторе */
#define ES_TOP      1
#define ES_LEFT     2
#define ES_BOTTOM   3
#define ES_RIGHT    4

#define ES_FILE     5
#define ES_TEXT     6

#define ES_WIDTH    7
#define ES_TABSIZE  8
#define ES_SCROLL   9
#define ES_WRAP     10
#define ES_INS      11

#define ES_ROW      12
#define ES_COL      13
#define ES_RELROW   14
#define ES_RELCOL   15

#define ES_CHANGED  16
#define ES_LASTKEY  17

#define ES_PATTERN  18

#define ES_R1       19
#define ES_C1       20
#define ES_R2       21
#define ES_C2       22
#define ES_DISP     23
#define ES_SELECT   24 // Буль. Непрерывная область выделения

#define ES_LENGTH   24

STATIC cMainPluginsDir,cPluginsDir
STATIC cMainSourceDir ,cSourceDir
STATIC cIncludeDir

// Имя девелоперского хелпа, запускаемого из редактора
STATIC chmHelpName    :="Программирование_в_БЭСТ-4.chm"

// Нужна настройка этих переменных, если собираемся использовать внешний редактор
STATIC lExtEditor     :=.F.        // Использовать внешний редактор
STATIC cEditorName    :="c:\me\me" // Внешний редактор
STATIC lDebug         :=.F.        // Использовать отладчик с внешним редактором

STATIC aEdit, cSourceName
Static cOldPath,cOldDisk, N:=1
// Головная функция для запуска из БЭСТ-4+
FUNCTION xDebug()
Local aSet := SaveSet()
Local aF:=SaveSetKey()
Local cTrap:=TrapShift("")

    ScrTitul(23,"")

    cOldPath := DIRNAME()
    cOldDisk := DISKNAME()

    cMainPluginsDir := cProPath + 'PLUGINS\EXTENSNS\'
    cMainSourceDir  := cProPath + 'PLUGINS\SOURCE\'
    cIncludeDir     := 'BIN\INCLUDE\'

    // Путь, куда помещаем готовые объектники
    // Если не существует, создаем (хотя это вряд ли, но вдруг бэст битый:)
    cPluginsDir:=cMainPluginsDir+GetAllArm(NIL,EXENAME())[6]
    IF DIRCHANGE(cPluginsDir)<>0
      MakeDir(cPluginsDir)  // run("MD "+cPluginsDir)
    ENDIF
    DIRCHANGE(cOldPath)

    // Путь, по которому лежат исходники от данного арма
    // Если не существует, создаем
    cSourceDir :=cMainSourceDir+GetAllArm(NIL,EXENAME())[6]
    IF DIRCHANGE(cSourceDir)<>0
      MakeDir(cSourceDir) // run("MD "+cSourceDir)
      DIRCHANGE(cSourceDir)
    ENDIF

  #ifndef DEBUG
    TRY
  #endif
      DBPUSH()
      IF lExtEditor // Если используется внешний редактор
        // Редактирование происходит в папке SOURCE
        CreateMyRun (cEditorName+" "+cSourceName)

        IF NoOrYes("Выполнить отредактированную программу?",,{"  Да  ","  Нет  "})=1 // Компиляция и запуск
          xCompileRun(cSourceDir,cSourceName,lDebug,cOldPath)
        ENDIF
      ELSE // Используется внутренний редактор
        do while .t.
          if !EditSource(cSourceDir,cSourceName )
            exit
          endif
        enddo
      ENDIF
  #ifndef DEBUG
    CATCH
      SayAndWait("Ошибка в работе среды программирования")
    END
  #endif
    // Восстанавливаем все что можно
    DBPOP()
    DISKCHANGE(cOldDisk)
    DIRCHANGE(cOldPath)
    RestSet(aSet)
    RestSetKey(aF)
    TrapShift(cTrap)
  RETURN "RET"



STATIC FUNCTION xCompileRun(cSourceDir,cSourceName,lDebug,cBestPath,lCompileOnly)
LOCAL cObjectName,cCurPath:=DIRNAME(), aSet :=SaveSet(),cErrorLog,nErrorStr:=0,cS
Local cSrcCompile
Local uError := 999
BEGIN SEQUENCE

    DIRCHANGE(cBestPath)

    // Сформировать имя объектника (и удалить старый)
    cObjectName := ChangeFExt(cSourceName,".hrb")
    if File( cPluginsDir + cObjectName )
       FERASE(cPluginsDir + cObjectName)
    endif
    cSrcCompile := cSourceDir + cSourceName

#ifndef __PLATFORM__Linux
    if File('bin\harbour.exe')
       uError := __run ("bin\harbour.exe "+cSrcCompile+" /n /a /gh "+IF(lDebug,"/b","/l")+" /o"+cPluginsDir+" /i"+cIncludeDir+" -ibin\include >hError.log" )
       if uError<>0
         // Если ошибка - пробуем без главной процедуры
         // Если и здесь будет ошибка, лог будет показываться от первого запуска
         uError := __run ("bin\harbour.exe "+cSrcCompile+" /a /gh "+IF(lDebug,"/b","/l")+" /o"+cPluginsDir+" /i"+cIncludeDir+" -ibin\include >hError1.log" )
       endif
    else
    endif
#else
    if File('bin/harbour')
       uError := __run ("bin/harbour "+cSourceDir+cSourceName+" /n /a /gh "+IF(lDebug,"/b","/l")+" /o"+cPluginsDir+" /i"+cIncludeDir+" -ibin/include  >hError.log" )
       if uError<>0
         uError := __run ("bin/harbour "+cSourceDir+cSourceName+" /a /gh "+IF(lDebug,"/b","/l")+" /o"+cPluginsDir+" /i"+cIncludeDir+" -ibin/include  >hError1.log" )
       endif
    else
    endif
#endif
    IF uError == 0.and.file(cPluginsDir+cObjectName) // Компиляция успешная
      IF lDebug // Отладка
        // Записываем ini-файл для отладчика
        // Если кому-то нужен свой - пусть загружают вручную из другого файла
        MemoWrit("init.cld",;
                 "Options Path "+cSourceDir+CHR(13)+CHR(10)+;
                 "Monitor LOCAL"+CHR(13)+CHR(10))
      ENDIF
      IF EMPTY(lCompileOnly)
        sel:=WSelect(0)
        hFileEval(cObjectName)
        WSelect(sel)
      ELSE
        SayAndWait({"Компиляция выполнена!","",;
                    "Исходный текст: "+DISKNAME()+":"+DIRNAME()+"\"+cSourceDir+cSourceName,;
                    "HRB-файл: "+DISKNAME()+":"+DIRNAME()+"\"+cPluginsDir+cObjectName})
      ENDIF
      nErrorStr:=0
    ELSE // Ошибка
      SayAndWait({"Ошибка компиляции "+cSourceName+" !"})
      SetColor("w/n")
      WOpen(2,0,23,79,.T.)
      WBox(FRAMES1)
      cErrorLog:=MemoRead("hError.log")
      MemoEdit(cErrorLog,0,0,19,78)
      nErrorStr:=AT(") Error",cErrorLog)
      cS:=LEFT(cErrorLog,nErrorStr-1)
      nErrorStr:=RAT("(",cS)
      cS:=SUBSTR(cS,nErrorStr+1)
      nErrorStr:=val(cS)
      WClose()
      KEYBOARD CHR(32)
      Inkey(0)
    ENDIF

END SEQUENCE

    DIRCHANGE(cCurPath)
    RestSet(aSet)
    // Возвращает номер строки с ошибкой (или 0, если все нормально)
  RETURN nErrorStr


// Замена расширения у файла
/***
STATIC FUNCTION ChangeFExt(FN,Ext)
  LOCAL  P:=RAT(".",FN)
  RETURN IF(EMPTY(P),FN,LEFT(FN,P-1)+Ext)
***/
Static Function ChangeFExt(cFull,uExt)
Local cDir, cName, cExt
hb_FNameSplit( cFull, @cDir, @cName, @cExt )
cExt := uExt
Return hb_FNameMerge( cDir, cName, cExt )

// Отрыв пути от полного имени файла
/*
STATIC FUNCTION ExtrFPath(FN)
  LOCAL  P:=RAT("\",FN)
  RETURN IF(EMPTY(P),FN,LEFT(FN,P-1))+"\"
*/

Static Function ExtrFPath(cFull)
Local cDir, cName, cExt
hb_FNameSplit( cFull, @cDir, @cName, @cExt )
Return cDir


STATIC FUNCTION EditSource(cSourceDir, cSourceName)
  LOCAL nKey, lDone, nErrorRow:=0, aNames:={},cBuffer:="",S,Ret:=.F.
  LOCAL lMyDebug:=.F.

    Set(_SET_BELL, .f.)
    Set(_SET_SCOREBOARD, .f.)
    SetKey(K_F1, NIL)

    IF ( IsColor() )
      SetColor("w+/b, b/w, b")
    ELSE
      SetColor("w/n, n/w")
    end

    aEdit := Array(ES_LENGTH)

    IF EMPTY(cSourceName)
      aNames:=FileOpen()
      cSourceName:=aNames[2]
      IF !Empty(cSourceName) .and. ( Rat(".", cSourceName) <= Rat("\", cSourceName) ).and.!file(cSourceName)
        cSourceName+=".prg"
      end
    ENDIF

    IF !EMPTY(cSourceName)
    Ret:=.T.
    aEdit[ES_FILE]      := Lower(cSourceName)
    IF EMPTY(aNames[1])
      if file(OemToAnsi(aEdit[ES_FILE]))
        aEdit[ES_TEXT]      := MemoRead(OemToAnsi(aEdit[ES_FILE]))
      else
        fclose(fcreate(OemToAnsi(aEdit[ES_FILE])))
      endif
    ELSE // Если "Открыть как" - читаем из другого файла
      aEdit[ES_TEXT]      := MemoRead(OemToAnsi(aNames[1]))
    ENDIF

    aEdit[ES_TOP]       := 0
    aEdit[ES_LEFT]      := 0
    aEdit[ES_BOTTOM]    := 18
    aEdit[ES_RIGHT]     := 77

    aEdit[ES_WIDTH]     := 512
    aEdit[ES_TABSIZE]   := 4
    aEdit[ES_SCROLL]    := .f.
    aEdit[ES_WRAP]      := .t.
    Set(_SET_INSERT,.T.)
    aEdit[ES_INS]       := Set(_SET_INSERT)

    aEdit[ES_ROW]       := 1
    aEdit[ES_COL]       := 0
    aEdit[ES_RELROW]    := 0
    aEdit[ES_RELCOL]    := 0

    aEdit[ES_CHANGED]   := .f.
    aEdit[ES_LASTKEY]   := 0

    aEdit[ES_PATTERN]   := ""

    // Координаты выделенной области
    aEdit[ES_R1]        := Nil
    aEdit[ES_C1]        := Nil
    aEdit[ES_R2]        := Nil
    aEdit[ES_C2]        := Nil

    aEdit[ES_DISP]      := .F. // Выключено ли отображение
    aEdit[ES_SELECT]    := .F. // Выделение начнется заново


    WOpen(2,0,22,79,.T.,.F.)
    WBox(FRAMES2)


    @ -1, 2 SAY "[" + cSourceDir+aEdit[ES_FILE] + "]"

    ScrTitul(23,"F9/F12:Запуск/с отладкой Alt-Ins:Состояние БД Alt-S:Поиск Alt-A:Искать далее")
    ScrTitul(24,"F2:Компиляция F1:Справка Alt-H:Подсказка Alt-W:Сохр Alt-O:Сохр.как...")
    SetColor("GR+/B")

    lDone := .f.


    while (!lDone)
        DoEditing()

        nKey := aEdit[ES_LASTKEY]

        do case
        CASE nKey=K_SH_DEL
          aEdit[ES_TEXT]:=DeleteFragment(aEdit[ES_TEXT])
        CASE nKey=K_F9 // Запуск без отладки
          EditWrite()
          if !Empty(aEdit[ES_TEXT])
             nErrorRow:=xCompileRun(cSourceDir,OemToAnsi(aEdit[ES_FILE]),.F.,cOldPath)
            else
             SayError("Пустой исходный файл "+aEdit[ES_FILE]+" !")
          endif
        CASE nKey=K_F2 // Сборка
          EditWrite()
          if !Empty(aEdit[ES_TEXT])
             lMyDebug:=YesOrNo("Включить информацию для отладки?")
             nErrorRow:=xCompileRun(cSourceDir,OemToAnsi(aEdit[ES_FILE]),lMyDebug,cOldPath, .T.)
            else
             SayError("Пустой исходный файл "+aEdit[ES_FILE]+" !")
          endif
        CASE nKey=K_F12 // Запуск под отладчиком
          EditWrite()
          if !Empty(aEdit[ES_TEXT])
             nErrorRow:=xCompileRun(cSourceDir,OemToAnsi(aEdit[ES_FILE]),.T.,cOldPath)
            else
             SayError("Пустой исходный файл "+aEdit[ES_FILE]+" !")
          endif

        case (nKey == K_ALT_S)
            Search()

        case (nKey == K_ALT_A)
            SearchAgain()

        case (nKey == K_ALT_O)
            FileSaveAs()

        case (nKey == K_ALT_W)
            EditWrite()

        case (nKey == K_ESC)
            lDone := PExit()

        case (nKey == K_CTRL_W)
            EditWrite()
            lDone := PExit()
//***********************
        case nKey=K_SH_RIGHT
          // Если выделение продолжается
          IF aEdit[ES_SELECT] .AND. aEdit[ES_R1]<>Nil .AND. aEdit[ES_C1]<>Nil
          ELSE // Если только начинается
            aEdit[ES_R1]        := aEdit[ES_ROW]
            aEdit[ES_C1]        := aEdit[ES_COL]
          ENDIF
          IF aEdit[ES_COL]<LEN(RTRIM(MyMEMOLINE(aEdit[ES_TEXT],aEdit[ES_ROW])))-1
            // Переходим на символ вправо
            aEdit[ES_R2]        := aEdit[ES_ROW]
            aEdit[ES_C2]        := aEdit[ES_COL]
            KEYBOARD CHR(K_RIGHT)
          ELSE
            // Переходим на строку вниз
            aEdit[ES_R2]        := aEdit[ES_ROW]+1
            aEdit[ES_C2]        := -1
            KEYBOARD CHR(K_DOWN)+CHR(K_HOME)
          ENDIF

          aEdit[ES_SELECT]:=.T.
          aEdit[ES_RELCOL]-=1

        case nKey=K_SH_LEFT
          IF aEdit[ES_SELECT] .AND. aEdit[ES_R2]<>Nil .AND. aEdit[ES_C2]<>Nil // Если выделение продолжается
          ELSE // Если только начинается
            aEdit[ES_R2]        := aEdit[ES_ROW]
            aEdit[ES_C2]        := aEdit[ES_COL]-1
          ENDIF
          IF aEdit[ES_COL]<>0
            // Переходим на символ влево
            aEdit[ES_R1]        := aEdit[ES_ROW]
            aEdit[ES_C1]        := aEdit[ES_COL]-1
            KEYBOARD CHR(K_LEFT)
          ELSE
            // Переходим на строку вверх
            aEdit[ES_R1]        := aEdit[ES_ROW]-1
            aEdit[ES_C1]        := LEN(RTRIM(MyMEMOLINE(aEdit[ES_TEXT],aEdit[ES_R1])))
            KEYBOARD CHR(K_UP)+CHR(K_END)
          ENDIF

          aEdit[ES_SELECT]:=.T.
          aEdit[ES_RELCOL]-=1

        case nKey=K_SH_DOWN
          // Если выделение продолжается
          IF aEdit[ES_SELECT] .AND. aEdit[ES_R1]<>Nil .AND. aEdit[ES_C1]<>Nil
          ELSE // Если только начинается
            aEdit[ES_R1]        := aEdit[ES_ROW]
            aEdit[ES_C1]        := aEdit[ES_COL]
          ENDIF
          // Переходим на строку вниз
          aEdit[ES_R2]        := aEdit[ES_ROW]+1
          aEdit[ES_C2]        := aEdit[ES_COL]-1
          KEYBOARD CHR(K_DOWN)

          aEdit[ES_SELECT]:=.T.

        case nKey=K_SH_UP
          IF aEdit[ES_SELECT] .AND. aEdit[ES_R2]<>Nil .AND. aEdit[ES_C2]<>Nil // Если выделение продолжается
          ELSE // Если только начинается
            aEdit[ES_R2]        := aEdit[ES_ROW]
            aEdit[ES_C2]        := aEdit[ES_COL]-1
          ENDIF
          // Переходим на строку вверх
          aEdit[ES_R1]          := aEdit[ES_ROW]-1
          aEdit[ES_C1]          := aEdit[ES_COL]
          KEYBOARD CHR(K_UP)

          aEdit[ES_SELECT]:=.T.
//******************
        case (nKey == K_CTRL_INS)
          if !(aEdit[ES_R1]=Nil.OR.aEdit[ES_C1]=Nil.OR.aEdit[ES_R2]=Nil.OR.aEdit[ES_C2]=Nil)
            cBuffer:=""
            FOR r=aEdit[ES_R1] to aEdit[ES_R2]
              c1:=IF(r>aEdit[ES_R1],1   ,aEdit[ES_C1]+1)
              c2:=IF(r<aEdit[ES_R2],1024,aEdit[ES_C2]+1)
              S:=MyMemoLine(aEdit[ES_TEXT],r)
              cBuffer+=RTrim(SUBSTR(S,c1,c2-c1+1))+IF(r<aEdit[ES_R2],CHR(13)+CHR(10),'')
            NEXT r
          endif
          ToBuffer(cBuffer)

        case nKey=K_SH_INS
          FromBuffer()

        otherwise
        end
        // Устанавливаем позицию на место ошибки
        IF nErrorRow>0
          aEdit[ES_ROW]    := nErrorRow
          aEdit[ES_RELROW] := 0
          nErrorRow        := 0
        ENDIF

    end

    IF ( IsColor() )
        SetColor(",,n")
    end

    WClose()
    ENDIF
  RETURN Ret

STATIC FUNCTION MyMemoLine(cStr,N)
  LOCAL i

    cStr:=SUBSTR(cStr,GetAbsPos(cStr,N,1))

//    i:=AT(CHR(13)+CHR(10),cStr)
    i:=AT(CHR(13),cStr)
    if i>0
      cStr:=LEFT(cStr,i-1)
    endif
    //sayandwait(cStr)
  RETURN cStr

/*STATIC FUNCTION MyMemoLine(cStr,N)
  LOCAL i
    FOR i:=1 to N-1
      cStr:=SUBSTR(cStr,AT(CHR(13),cStr)+2)
    NEXT i
    i:=AT(CHR(13),cStr)
    if i>0
      cStr:=LEFT(cStr,i-1)
    endif
  RETURN cStr
*/

// Определение абсолютного адреса символа в мемо по номеру строки и колонки
STATIC FUNCTION GetAbsPos(S,R,C)
  LOCAL Ret
    Ret:=IF(R>1,AtNum(CHR(13),S,R-1),0)+1+C
  RETURN Ret

// Возвращает количество вхождений подстроки в строку.
STATIC FUNCTION AtCount(c,S)
  LOCAL Ret:=0,i
    for i = 1 to len(S)
      if SUBSTR(S,i,LEN(c))=c
        Ret++
      endif
    next i
  RETURN Ret

// Возвращает массив Row,Col по абсолютному адресу символа в строке
STATIC FUNCTION GetPosAbs(S,n)
  LOCAL Ret:={0,0}
    Ret[1]:=AtCount(CHR(13),left(S,n))+1
    Ret[2]:=n-GetAbsPos(S,Ret[1],1)
  RETURN Ret

STATIC FUNCTION DeleteFragment(cStr)
  LOCAL i,cOut:="",c1,c2,S,cBuffer,r
    if !(aEdit[ES_R1]=Nil.OR.aEdit[ES_C1]=Nil.OR.aEdit[ES_R2]=Nil.OR.aEdit[ES_C2]=Nil)

      cBuffer:=""
      FOR r=aEdit[ES_R1] to aEdit[ES_R2]
        c1:=IF(r>aEdit[ES_R1],1   ,aEdit[ES_C1]+1)
        c2:=IF(r<aEdit[ES_R2],1024,aEdit[ES_C2]+1)
        S:=MyMemoLine(aEdit[ES_TEXT],r)
        cBuffer+=RTrim(SUBSTR(S,c1,c2-c1+1))+IF(r<aEdit[ES_R2],CHR(13)+CHR(10),'')
      NEXT r
      ToBuffer(cBuffer)

      cOut:=SUBSTR(cStr,1,GetAbsPos(cStr,aEdit[ES_R1],aEdit[ES_C1]))+;
            SUBSTR(cStr,GetAbsPos(cStr,aEdit[ES_R2],aEdit[ES_C2])+2)
      aEdit[ES_SELECT]:=.F.
      aEdit[ES_R1]:=Nil
      aEdit[ES_C1]:=Nil
      aEdit[ES_R2]:=Nil
      aEdit[ES_C2]:=Nil
      aEdit[ES_CHANGED]:=.T.
      ColorWin(aEdit[ES_TOP]+1,aEdit[ES_LEFT]+1,aEdit[ES_BOTTOM]+1,aEdit[ES_RIGHT]+1,"gr+/b")
    else
      cOut:=cStr
    endif

  RETURN cOut


// Функция пользователя для редактора исходных текстов
FUNCTION ufuncDebugEdit(nMode, nLine, nCol)
  LOCAL nKey,r,c1,c2,r1

    aEdit[ES_LASTKEY]   := nKey := LastKey()
    aEdit[ES_ROW]       := nLine
    aEdit[ES_COL]       := nCol
    aEdit[ES_RELROW]    := Row() - aEdit[ES_TOP]
    aEdit[ES_RELCOL]    := Col() - aEdit[ES_LEFT]

    IF (nMode == ME_INIT)
        IF (aEdit[ES_WRAP])
            /* turn off word wrap */
            aEdit[ES_WRAP] := .f.
            RETURN (ME_TOGGLEWRAP)  /* NOTE */
        end

        SetCursor( if(aEdit[ES_INS], SC_INSERT, SC_NORMAL) )
        //SETKEY( K_BS , Nil )
        SETKEY( K_CTRL_K , {|| aEdit[ES_SELECT]:=.F.,aEdit[ES_R1]:=Nil,aEdit[ES_C1]:=Nil,aEdit[ES_R2]:=Nil,aEdit[ES_C2]:=Nil,ColorWin(aEdit[ES_TOP]+1,aEdit[ES_LEFT]+1,aEdit[ES_BOTTOM]+1,aEdit[ES_RIGHT]+1,"gr+/b")} )

    ELSEIF (nMode == ME_IDLE)

        // IF ! nKey IN {K_SH_UP, K_SH_DOWN, K_SH_LEFT, K_SH_RIGHT} .AND. (!(ISBIT(KBDSTAT(),1).OR.ISBIT(KBDSTAT(),0)))
        IF !(ISBIT(KBDSTAT(),1).OR.ISBIT(KBDSTAT(),0))
          aEdit[ES_SELECT]:=.F.
        ENDIF
        StatMsg()

        // Отображение выделенного фрагмента
        if !(aEdit[ES_R1]=Nil.OR.aEdit[ES_C1]=Nil.OR.aEdit[ES_R2]=Nil.OR.aEdit[ES_C2]=Nil)

          ColorWin(aEdit[ES_TOP]+1,aEdit[ES_LEFT]+1,aEdit[ES_BOTTOM]+1,aEdit[ES_RIGHT]+1,"gr+/b")
          IF (aEdit[ES_R2]<aEdit[ES_R1]).OR.((aEdit[ES_R2]=aEdit[ES_R1]).AND.(aEdit[ES_C2]<aEdit[ES_C1]))
            r1:=aEdit[ES_R1]
            aEdit[ES_R1]:=aEdit[ES_R2]
            aEdit[ES_R2]:=r1
            c1:=aEdit[ES_C1]
            aEdit[ES_C1]:=aEdit[ES_C2]
            aEdit[ES_C2]:=c1
          ENDIF

          FOR r=aEdit[ES_R1] to aEdit[ES_R2]
            r1:=r+Row()-nLine+1 // Относительные координаты в пределах экрана
            c1:=aEdit[ES_C1]+Col()-nCol+1
            c2:=aEdit[ES_C2]+Col()-nCol+1

            if r1>=aEdit[ES_TOP]+1 .and. r1<=aEdit[ES_BOTTOM]+1 // Контроль выхода за пределы экрана
              if c1<aEdit[ES_LEFT]+1.OR.r>aEdit[ES_R1]
                c1:=aEdit[ES_LEFT]+1
              endif
              if c2>aEdit[ES_RIGHT]+1.OR.r<aEdit[ES_R2]
                c2:=aEdit[ES_RIGHT]+1
              endif
              IF c1<=c2
                ColorWin(r1,c1,r1,c2,"n/w")
              ENDIF
            endif
          NEXT r
        endif
        IF aEdit[ES_DISP]
          aEdit[ES_DISP]:=.F.
          DispEnd()
        ENDIF
    ELSE
        /* keystroke exception */
        IF (nMode == ME_UNKEYX)
            aEdit[ES_CHANGED] := .T.
        end

        IF aEdit[ES_DISP]
          aEdit[ES_DISP]:=.F.
          DispEnd()
        ENDIF

        do case
        case nKey=K_CTRL_INS
          //Помещаем выделенный фрагмент в буфер обмена
          RETURN (K_CTRL_W)
        case nKey=K_SH_INS
          // Paste из буфера /Глюк - если в строке есть ";", происходит перенос на новую строку
          // Антиглюк - вставлять непосредственно в текст не через буфер клавиатуры
          RETURN (K_CTRL_W)
        case nKey=K_SH_RIGHT
            RETURN (K_CTRL_W)

        case nKey=K_SH_LEFT
            RETURN (K_CTRL_W)

        case nKey=K_SH_DOWN
            RETURN (K_CTRL_W)

        case nKey=K_SH_UP
            RETURN (K_CTRL_W)

        CASE (nkey==K_F9) .OR. (nkey==K_F12) .OR. (nkey==K_F2) .OR. (nKey==K_SH_DEL)
            RETURN (K_CTRL_W)

        case (nKey == K_F1)
            //run(OemToAnsi("start "+chmHelpName))
//            RunShell(OemToAnsi(chmHelpName))
            RunShell(OemToAnsi(chmHelpName),,,cOldPath+"\news")

        case (nKey == K_ALT_H)
            DisplayHelp()

        case (nKey == K_ALT_O)
            RETURN (K_CTRL_W)

        case (nKey == K_INS)
            aEdit[ES_INS] := !Set(_SET_INSERT)
            SetCursor( if(aEdit[ES_INS], SC_INSERT, SC_NORMAL) )
            RETURN (nKey)

        case (nKey == K_ALT_I)
            aEdit[ES_INS] := !Set(_SET_INSERT)
            SetCursor( if(aEdit[ES_INS], SC_INSERT, SC_NORMAL) )
            RETURN (K_INS)

        case (nKey == K_ALT_S)
            /* search */
            RETURN (K_CTRL_W)

        case (nKey == K_ALT_A)
            /* search again */
            RETURN (K_CTRL_W)

        case (nKey == K_ALT_X)
            aEdit[ES_LASTKEY] := K_ESC
            RETURN (K_CTRL_W)

        case (nKey == K_ESC)
            RETURN (K_CTRL_W)

        case (nKey == K_ALT_W)
            RETURN (K_CTRL_W)

        otherwise
        end

    end
    if LastKey()=K_LEFT .or. LastKey()=K_RIGHT .or. LastKey()=K_UP .or.LastKey()=K_DOWN
      aEdit[ES_DISP]:=.T.
      DispBegin()
    else
      IF aEdit[ES_DISP]
        aEdit[ES_DISP]:=.F.
        DispEnd()
      ENDIF
    endif
RETURN (0)


// Сохранение текста на диск
STATIC FUNCTION EditWrite()
  LOCAL lRet
    lRet := .t.
    IF ( aEdit[ES_CHANGED] )
      IF ( MemoWrit(OemToAnsi(aEdit[ES_FILE]), aEdit[ES_TEXT]))
        aEdit[ES_CHANGED] := .f.
      ELSE
        SayAndWait("Ошибка записи")
        lRet := .f.
      end
    end
  RETURN (lRet)


// Вывод строки, колонки в углу экрана
STATIC FUNCTION StatMsg()
  LOCAL cLine, cCol, nCtype, nRow, nCol
    cLine := PadR( LTrim(Str(aEdit[ES_ROW],6,0)), 6 )
    cCol :=  PadR( LTrim(Str(aEdit[ES_COL],3,0)), 3 )
    nCtype := SetCursor(0)
    nRow := Row()
    nCol := Col()
    @ -1, 55 SAY "[ Стр: " + cLine + "Кол: " + cCol + "]" COLOR "G+/B"

    DevPos(nRow, nCol)
    SetCursor(nCtype)
  RETURN (NIL)


// Отработка события на выход
STATIC FUNCTION PExit()
  LOCAL c, lRet, nCtype
    lRet = .t.
    IF ( aEdit[ES_CHANGED] )
      nCtype := SetCursor(SC_NORMAL)

      ret_val:=NoOrYes(" Сохранить изменения в файле "+aEdit[ES_FILE]+" ?",,{"    Да    ","    Нет    ","  Отмена  "})
      IF ret_val=1
        lRet := EditWrite()
      ELSEIF ret_val<>2
        lRet := .f.
      ENDIF
      lkey=Inkey()
      SetCursor(nCtype)
    ENDIF
  RETURN (lRet)


// Подсказка по функциям
STATIC FUNCTION DisplayHelp()
  LOCAL  nCtype

    WOpen(3,5,22,75,.T.,.F.)
    WBox(FRAMES1)

    @ 0, 1 say "      Перемещение курсора        │      Файловые операции          " COLOR "w+/b"
    @ 1, 1 say "                                 │                                 " COLOR "w/b"
    @ 2, 1 say "Ctrl-Left   На слово влево       │ Alt-W       Сохранить файл      " COLOR "w/b"
    @ 3, 1 say "Ctrl-Right  На слово вправо      │ Alt-O       Сохранить файл как  " COLOR "w/b"
    @ 4, 1 say "                                 │                                 " COLOR "w/b"
    @ 5, 1 say "Ctrl-Y      Удалить строку       │ Alt-S       Искать              " COLOR "w/b"
    @ 6 ,1 say "Ctrl-T      Удалить слово справа │ Alt-A       Искать снова        " COLOR "w/b"
    @ 7 ,1 say "                                 │                                 " COLOR "w/b"
    @ 8 ,1 say "Alt-I, Ins  Режим вставки/замены │ F9          Компиляция и запуск " COLOR "w/b"
    @ 9 ,1 say "                                 │ F12         Компиляция и отладка" COLOR "w/b"
    @ 10,1 say "                                 │ F2          Компиляция          " COLOR "w/b"
    @ 11,1 say "   Операции с блоками текста    " COLOR "w+/b"
    @ 11,34 say                                 "│ F1          Справка по програм- " COLOR "w/b"
    @ 12,1 say "Shift-Стрелки   Выделение текста │             мированию в БЭСТ4+" COLOR "w/b"
    @ 13,1 say "                                 │ Alt-H       Справка по работе в " COLOR "w/b"
    @ 14,1 say "Ctrl-Ins    Поместить в буфер    │             редакторе           " COLOR "w/b"
    @ 15,1 say "Shift-Ins   Вставить из буфера   │                                 " COLOR "w/b"
    @ 16,1 say "Shift-Del   Удалить фрагмент     │ Alt-Ins     Состояние БД (везде)" COLOR "w/b"
    @ 17,1 say "Ctrl-K      Снять выделение      │ Esc         Завершение работы   " COLOR "w/b"

    nCtype := SetCursor(0)
    Inkey(0)
    SetCursor(nCtype)

    WClose()

  RETURN (NIL)




****
*   xSearch()
*

STATIC FUNCTION xSearch(x)
  LOCAL nRow, pos, offset, newcol, a

    IF ( !Empty(aEdit[ES_PATTERN]) )
        nRow := aEdit[ES_ROW]
        pos := x + GetAbsPos(aEdit[ES_TEXT],aEdit[ES_ROW],aEdit[ES_COL])
        //sayandwait({str(aEdit[ES_ROW]),str(aEdit[ES_COL]),str(pos)})
//        pos := x + MLCToPos(aEdit[ES_TEXT],     ;
//                            aEdit[ES_WIDTH],    ;
//                            aEdit[ES_ROW],      ;
//                            aEdit[ES_COL],      ;
//                            aEdit[ES_TABSIZE],  ;
//                            aEdit[ES_WRAP]      ;
//                           )

        offset := pos + At(upper(aEdit[ES_PATTERN]),upper(Substr(aEdit[ES_TEXT], pos))) - 1
        IF ( offset >= pos )
            a := GetPosAbs(aEdit[ES_TEXT],offset)
//            a := MPosToLC(aEdit[ES_TEXT],   ;
//                          aEdit[ES_WIDTH],  ;
//                          offset,           ;
//                          aEdit[ES_TABSIZE],;
//                          aEdit[ES_WRAP]    ;
//                         )
//        sayandwait({str(a[1]),str(a[2])})
            aEdit[ES_ROW] := a[1]
            newcol := a[2]
            aEdit[ES_RELCOL] := aEdit[ES_RELCOL] + newcol - aEdit[ES_COL]
            aEdit[ES_COL] := newcol

            IF ( aEdit[ES_ROW] - nRow <=                                ;
                 aEdit[ES_BOTTOM] - aEdit[ES_TOP] - aEdit[ES_RELROW]    ;
               )

                aEdit[ES_RELROW] := aEdit[ES_RELROW] + aEdit[ES_ROW] - nRow

            ENDIF

            // SayAndWait("Поиск завершен.")

        ELSE
          SayAndWait("Данные не найдены...")
        ENDIF
    ELSE
      // Msg("")
    ENDIF

  RETURN (NIL)


STATIC FUNCTION Search()
LOCAL pattern

    pattern := Dialog("Строка поиска:",REPLICATE("X",34), PADR(aEdit[ES_PATTERN],34) )
    pattern := Ltrim(Rtrim(pattern))
    IF ( !Empty(pattern) )
        aEdit[ES_PATTERN] := pattern
        xSearch(0)
    ELSE
      // Msg("")
    end

  RETURN (NIL)


STATIC FUNCTION SearchAgain()
  RETURN (xSearch(2))



STATIC FUNCTION DoEditing()
  LOCAL BK_ALT_INS,BK_CTRL_H

    BK_ALT_INS:=SETKEY( K_ALT_INS, {|| AltIns() } )
    BK_CTRL_H :=SETKEY( K_CTRL_K , {|| aEdit[ES_SELECT]:=.F.,aEdit[ES_R1]:=Nil,aEdit[ES_C1]:=Nil,aEdit[ES_R2]:=Nil,aEdit[ES_C2]:=Nil,ColorWin(aEdit[ES_TOP]+1,aEdit[ES_LEFT]+1,aEdit[ES_BOTTOM]+1,aEdit[ES_RIGHT]+1,"gr+/b")} )

    aEdit[ES_WRAP] := .t.
    aEdit[ES_TEXT] := ctw_MemoEdit( aEdit[ES_TEXT],     ; //MemoEdit_
                                aEdit[ES_TOP],      ;
                                aEdit[ES_LEFT],     ;
                                aEdit[ES_BOTTOM],   ;
                                aEdit[ES_RIGHT],    ;
                                .t., "ufuncDebugEdit",       ;
                                aEdit[ES_WIDTH],    ;
                                aEdit[ES_TABSIZE],  ;
                                aEdit[ES_ROW],      ;
                                aEdit[ES_COL],      ;
                                aEdit[ES_RELROW],   ;
                                aEdit[ES_RELCOL]    )

    SETKEY( K_ALT_INS, BK_ALT_INS )
    SETKEY( K_CTRL_K, BK_CTRL_H )

  RETURN (NIL)


// Открытие нового файла
// Возвращает массив - Имя открываемого файла, Имя редактируемого файла
STATIC FUNCTION FileOpen(Name)
  LOCAL r1:=5,c1:=15,r2:=21,c2:=65,Col:=SetColor(),aDirectory,TB,TC,nLen,k,nKey,IsEmpty:=.F.,OpenName

    Name:=""
    OpenName:=""

    SetColor("W+/B,W+/N,,,N/W")
    WOpen(r1,c1,r2,c2,.t.,.t.)
    WBox(FRAMES2)

    TB:=TBrowseNew(1,0,r2-r1-3,c2-c1-2)

    nLen := Len(aDirectory := DIRECTORY("*.*"))
    IsEmpty := nLen == 0
    IF IsEmpty
      AADD(aDirectory,{"<пусто>",0,ctod(""),""})
      nLen++
    ELSE
      aEval(aDirectory,{|x,i| aDirectory[i][1] := AnsiToOem( aDirectory[i,1] ) })
    ENDIF
//    n := 1

    // Go Top and Go Bottom Blocks
    TB:goTopBlock    := {|| n := 1 }
    TB:goBottomBlock := {|| n := nLen }

    // This is our Skip Block
    TB:skipBlock := {|x| k := IF(ABS(x) >= IF(x >= 0,;
                     nLen - n, n - 1),;
                     IF(x >= 0, nLen - n, 1 - n),;
                     x), n += k,;
                     k }

    @ -1,1 SAY "["+cSourceDir+"]"
    @  0,0 SAY " Файл                    Размер     Дата   Время" COLOR "GR+/B"
    TC:=TBColumnNew("",{|| " "+PADR(LEFT(aDirectory[n,1],20),20)+IF(IsEmpty,SPACE(60),"│"+str(aDirectory[n,2]/1024,8,0)+" K │"+DTOC(aDirectory[n,3])+"│"+LEFT(aDirectory[n,4],5)+"          ")})
    TB:AddColumn(TC)
    TB:headSep:='─┬─'
    TB:colSep:= ' │ '
    TB:footSep:='─┴─'


    DO WHILE .T.
      TB:colorSpec:="W+/B,W+/BG,,,N/W"
      ScrTitul(24,"Enter:Открыть  Ctrl-Enter:Открыть как... Alt-N:Новый... Esc:Выход")

      IF !IsEmpty
        while .t.
          TB:ForceStable()
          @ r2-r1-2,1 SAY padr(aDirectory[n,1],47) COLOR "G+/b"
          nKey := INKEY(0)

          IF !TBMoveCursor( nKey, TB )
            IF nKey == K_ESC
              OpenName:=''
              Name:=''
              EXIT
            ELSEIF nKey == K_ENTER
              name:=aDirectory[n,1]
              EXIT
            ELSEIF nKey == K_F8
             if YesOrNo('Удалить файл '+aDirectory[n,1]+' ?' )
                 fErase(aDirectory[n,1])
                 nLen := Len(aDirectory := DIRECTORY("*.*"))
                 if nlen == 0
                    AADD(aDirectory,{"<пусто>",0,ctod(""),""})
                    nLen++
                 endif
                 TB:inValidate():refreshAll():forceStable()
              endif

            ELSEIF nKey == K_CTRL_ENTER
              Name:=aDirectory[n,1]
              OpenName:=Name
              EXIT
            ELSEIF nKey == K_ALT_N
              OpenName:=''
              Name:=''
              EXIT
            ENDIF
          ENDIF
        ENDDO
        nKey:=LastKey()
        IF nKey=K_ESC .or. nKey=K_ENTER
          // Завершаем выбор
          EXIT
        ENDIF

        TB:colorSpec:="W+/B,G+/B,,,N/W"
      ELSE
        OpenName:=""
        name:='untitled'
        TB:colorSpec:="W+/B,W/B"
      ENDIF
      TB:ForceStable()
      name:=padr(name,47)

      ScrTitul(24,"Enter:Подтвердить Esc:Назад")

      SetColor("W+/B,n/w,,,N/W")
      @ r2-r1-2,1 GET name VALID !EMPTY(name)

      READ

      nKey:=LastKey()
      IF nKey=K_ESC
        IF IsEmpty
          name:=''
          EXIT
        ENDIF
      ELSEIF nKey=K_ENTER
        Name:=alltrim(Name)
        IF !Empty(Name) .and. ( Rat(".", Name) <= Rat("\", Name) ).and.!file(Name)
          Name+=".prg"
        end
        IF !EMPTY(OpenName).and.FILE(name)
          IF NoOrYes("Файл "+name+" существует. Перезаписать?",,{"  Да  ","  Нет  "})=1
            EXIT
          ENDIF
        ELSE
          EXIT
        ENDIF
      end
    ENDDO
    WClose()

    SetColor(Col)

  RETURN {OpenName,Name}

STATIC FUNCTION TBMoveCursor( nKey, oObj )
  LOCAL nFound
    STATIC aKeys :=;
     {K_DOWN      , {|b| b:down()}     ,;
      K_UP        , {|b| b:up()}       ,;
      K_PGDN      , {|b| b:pageDown()} ,;
      K_PGUP      , {|b| b:pageUp()}   ,;
      K_CTRL_PGUP , {|b| b:goTop()}    ,;
      K_CTRL_PGDN , {|b| b:goBottom()} ,;
      K_RIGHT     , {|b| b:right()}    ,;
      K_LEFT      , {|b| b:left()}     ,;
      K_HOME      , {|b| b:home()}     ,;
      K_END       , {|b| b:end()}      ,;
      K_CTRL_LEFT , {|b| b:panLeft()}  ,;
      K_CTRL_RIGHT, {|b| b:panRight()} ,;
      K_CTRL_HOME , {|b| b:panHome()}  ,;
      K_CTRL_END  , {|b| b:panEnd()}    }

    nFound := ASCAN(aKeys, nKey)
    IF (nFound != 0)
      EVAL(aKeys[++nFound], oObj)
    ENDIF
  RETURN (nFound != 0)



// Присвоение нового имени
STATIC FUNCTION FileSaveAs()
  LOCAL name,Col:=SetColor()

    SetColor("W+/BG,W+/N,,,N/W")
    name := Dialog("Сохранить файл как:",REPLICATE("X",24), PADR(aEdit[ES_FILE],24) )
    name := Lower(Ltrim(Rtrim(name)))
    IF ( !Empty(name) .and. name != aEdit[ES_FILE] )
      aEdit[ES_FILE]    := name
      aEdit[ES_CHANGED] := .t.
      EditWrite()
    end
    SetColor(Col)
    @ -1, 2 SAY "[" + cSourceDir+aEdit[ES_FILE] + "]═══════════════" COLOR "W+/B"

  RETURN (NIL)


STATIC PROCEDURE ToBuffer(cBuf)
#ifdef __PLATFORM__Linux
#else
  WVT_SetClipboard(cBuf)
#endif
RETURN

STATIC PROCEDURE FromBuffer()
#ifdef __PLATFORM__Linux
#else
  LOCAL cBuffer := WVT_GetClipboard(),i
  IF VALTYPE(cBuffer)="C"
    IF LEN(cBuffer)>0
      aEdit[ES_TEXT]:=STUFF(aEdit[ES_TEXT],GetAbsPos(aEdit[ES_TEXT],aEdit[ES_ROW],aEdit[ES_COL])+1,0,cBuffer)
//      KEYBOARD cBuffer
    ENDIF
  ENDIF
#endif
RETURN


/*
               -= TODO =-

1.  Вставка из буфера непосредственно в мемо (";" делит строки)
3.  Иногда KbdStat сбрасывается в 0, и выделение начинается с начала.
    Поэтому выделение сделано постоянным и введена клавиша снятия выделения

2.  Не перезагружается исходный текст в отладчике (наверное не решаемо)
    Чтобы перегрузить текст, нужно выйти и войти в БЭСТ

2.1 Также, если в корне БЭСТ есть одноименный файл, в отладке запускается
    именно он. (issue 9817)

4.  При нажатии Alt-X в отладчике происходит выход из БЭСТ (issue 9818)

5.  При наличии runtime-ошибок в программе не показывается строка ошибки
    и др. подробности по ошибке. (issue 9819)

5.1 При наличии неопределенных функций происходит выход из всего БЭСТа.

// Реализовано.Valery 5.  Файл-менеджер - Удаление файлов F8

6.  Файл-менеджер - Работа с подкаталогами (надо ли?)

7.  Возможность открытия другого файла, не выходя из редактора (надо ли?)

8.  Подумать насчет многооконного интерфейса (или указывать имя головного файла)

9.  Подсветка синтаксиса (Вряд ли, и так все глючит)

11. Настройка - Имя внешнего редактора
              - Использовать внешний редактор
              - Использовать отладчик с внешним редактором
   ...Но можно просто забыть о внешнем редакторе.....

*/



