// CmdScanEdit.bcpl -- Command Scanner Package editing functions
// Copyright Xerox Corporation 1979

//	Last modified July 11, 1977  3:25 PM

get "CmdScan.decl"

external
[
//outgoing procedures
GetPhrase; AppendChar; EraseInput

//incoming procedures
NextPhrase; CurrentPhrase; BackupPhrase
Gets; Puts; Errors; Wss
DefaultArgs; Allocate; Free; Zero
]


//---------------------------------------------------------------------------
let GetPhrase(cs, WordBreak, PhraseTerminator, Echo,
    Help, helpArg; numargs na) = valof
//---------------------------------------------------------------------------
//Readies the next phrase to be taken, inputting one from the
//keyboard if necessary and updating the phrase state.
//Sets up cs>>CS.iChOut to point to the first character of the
//phrase, and returns the number of characters in the phrase,
//not including the terminator.
[
DefaultArgs(lv na, -1, cs>>CS.pd↑0.WordBreak,
 cs>>CS.pd↑0.PhraseTerminator, cs>>CS.pd↑0.Echo, 0, 0)
let pd = NextPhrase(cs)
pd>>PD.WordBreak = WordBreak
pd>>PD.PhraseTerminator = PhraseTerminator
pd>>PD.Echo = Echo

if cs>>CS.iPhIn eq cs>>CS.iPhOut then cs>>CS.reparse = false
unless cs>>CS.reparse % cs>>CS.reuse do EditPhrase(cs, Help, helpArg)

cs>>CS.reuse = false
cs>>CS.phraseRead = true
cs>>CS.iChOut = pd>>PD.iFirst  //ready to take first char
resultis pd>>PD.iLast-pd>>PD.iFirst
]

//---------------------------------------------------------------------------
and EditPhrase(cs, Help, helpArg) be
//---------------------------------------------------------------------------
//Edits the current phrase (the current input and output phrases
//are assumed to be the same), returning when a terminating
//character is input.
//The disposition of the existing contents of the phrase (if any)
//is controlled by cs>>CS.editControl, which is one of:
// editNew	New phrase -- fill it in (this is the default).
// editAppend	Phrase exists -- remove terminating character
//		and append more characters to it.
// editReplace	Phrase exists -- if the first character input is
//		not a terminating character, erase it and start
//		over;  if it is, just return the phrase as-is.
//If cs>>CS.putbackChar is nonzero, it is used as the first
//character of the phrase as if it had been typed in.
[
let pd = CurrentPhrase(cs)

   [ //repeat
   let char = cs>>CS.putbackChar ne 0? cs>>CS.putbackChar, Gets(cs>>CS.keyS)
   cs>>CS.putbackChar = 0
   if cs>>CS.editControl ne editNew & cs>>CS.iChIn gr pd>>PD.iLast then
      EraseInput(cs, pd>>PD.iLast, eraseTerminator)
   switchon char into
      [
      case $*010: case $*001:  //backspace, control-A
         [ EraseBackTo(cs, BackupChars(cs, false), eraseChar); endcase ]
      case $*027:  //control-W
         [ EraseBackTo(cs, BackupChars(cs, true), eraseWord); endcase ]
      case $*177:  //delete
         [ Errors(cs, ecCmdDelete); loop ]
      case $*022:  //control-R
         [ RetypeCmd(cs); loop ]
      case $?:
         [
         if Help ne 0 then
            [
            Wss(cs>>CS.dspS, "? ")
            Help(cs>>CS.dspS, helpArg)
            RetypeCmd(cs)
            loop
            ]
         //if no help, fall thru to default case
         ]
      default:
         [
         if cs>>CS.editControl eq editReplace &
          not pd>>PD.PhraseTerminator(cs, char) then
            EraseBackTo(cs, pd>>PD.iFirst, eraseWord)
         if AppendChar(cs, char, pd>>PD.Echo(cs, char)) &
          pd>>PD.PhraseTerminator(cs, char) then
            [
            pd>>PD.iLast = cs>>CS.iChIn-1
            test char eq $*s & pd>>PD.iLast eq pd>>PD.iFirst
               ifso pd>>PD.iFirst = pd>>PD.iFirst+1
               ifnot [ cs>>CS.editControl = editNew; break ]
            ]
         ]
      ]
   cs>>CS.editControl = editNew
   ] repeat
]

//---------------------------------------------------------------------------
and AppendChar(cs, char, echo) = valof
//---------------------------------------------------------------------------
//Appends a character to the command buffer, and also echos it if echo
//is true.  If echo is false, #200 is added to the character as a signal
//that the character is non-printing.  Returns true normally.
//If the buffer overflows, Errors is called;  if Errors returns, AppendChar
//returns false having done nothing.
[
let i = cs>>CS.iChIn
if i ge cs>>CS.maxChars then [ Errors(cs, ecCmdTooLong); resultis false ]
test echo
   ifso Puts(cs>>CS.dspS, char)
   ifnot char = char+#200
cs>>CS.buf>>Buf↑i = char
cs>>CS.iChIn = i+1
resultis true
]


//---------------------------------------------------------------------------
and BackupChars(cs, backupWord) = valof
//---------------------------------------------------------------------------
//If backupWord is false, returns the index of the last character input.
//if true, returns the index of the first character of the current word.
[
let iCh = cs>>CS.iChIn
let pd = lv cs>>CS.pd↑(FindPhrase(cs, iCh))
let nonBreakSeen = false
   [ //repeat
   let iNew = iCh-1
   if iCh eq pd>>PD.iFirst then
      [ //back up to previous phrase
      pd = pd-lenPD
      if pd eq lv cs>>CS.pd↑0 then break
      iNew = pd>>PD.iLast
      ]
   unless backupWord resultis iNew
   test pd>>PD.WordBreak(cs, cs>>CS.buf>>Buf↑iNew & #177)
      ifso if nonBreakSeen then break
      ifnot nonBreakSeen = true
   iCh = iNew
   ] repeat
resultis iCh
]

//---------------------------------------------------------------------------
and EraseInput(cs, i, context) be
//---------------------------------------------------------------------------
//Erases characters from the one most recently input back to i
//(inclusive) and resets the input pointer to the beginning of
//the erased portion.
[
if i ls cs>>CS.iChIn then cs>>CS.Erase(cs, i, cs>>CS.iChIn-1, context)
cs>>CS.iChIn = i
cs>>CS.iPhIn = FindPhrase(cs, i)
]


//---------------------------------------------------------------------------
and EraseBackTo(cs, i, context) be
//---------------------------------------------------------------------------
//Same as EraseInput;  additionally, if characters before the current
//phrase are erased, initiates a reparse of preceding phrases.
[
EraseInput(cs, i, context)
if cs>>CS.iPhIn ls cs>>CS.iPhOut then
   BackupPhrase(cs, cs>>CS.iPhOut-cs>>CS.iPhIn, editAppend)
]


//---------------------------------------------------------------------------
and FindPhrase(cs, iCh) = valof
//---------------------------------------------------------------------------
//Returns index of phrase to which character iCh belongs
[
let iPh = cs>>CS.iPhIn
while iCh ls cs>>CS.pd↑iPh.iFirst do iPh = iPh-1
resultis iPh
]


//---------------------------------------------------------------------------
and RetypeCmd(cs) be
//---------------------------------------------------------------------------
[
Puts(cs>>CS.dspS, $*n)
for i = 0 to cs>>CS.iChIn-1 do
   [
   let char = cs>>CS.buf>>Buf↑i
   if char ls #200 then Puts(cs>>CS.dspS, char)
   ]
]