/* CLASS: Virtual Smart File Object
  Provides intelligent behaviors for a VirtFileObject. Add extra cached info
  for caller.  Specialization for browsing source file associated with 
  load module. Also, contains breakpoints information to set/clear and display.

  REQUIRE: VIRTDATA.CLS, VIRTFILE.CLS, BKPTINFO.CLS
*/!!

inherit(VirtFileObject, #VirtSmartFileObject, #(
bkptDict      /* breakpoints dict of module */
callersCache  /* save current position */
moduleInfo    /* a ModuleInfo objects - from currentLoadFile */
modulePath    /* file path as opened */
), 2, nil)!!

now(class(VirtSmartFileObject))!!

now(VirtSmartFileObject)!!

/* 3/11/1994 5:25 - PUBLIC (to its parent) */
Def setCursorLinked(self, newLinked)
{ 
  cursorLinked := newLinked;
}
!!

/* 7/1/1992 11:42 - PUBLIC (to its browser only)
  Map a virtual line to its equivalent object context 
  line.  Return either the input lineNum or the mapped line number.
*/
Def mapLine(self, lineNum, ignoreArg | lineInfo )
{
  /* Remap tokenInfo's LineNum to its actual lineNum */
  if not(lineInfo := 
      getLineNumInfo(SymbolLibClass$Inst, moduleDescriptor(self),lineNum))
     ^lineNum;
  endif;
  /* NOTES: lineInfo = #(addrDesc, lineNum, colNum, lineNumDesc) */
  destroyAddress(AddressLibClass$Inst, lineInfo[0]);
  ^lineInfo[1];
}
!!

/* 3/11/1994 11:27 - PUBLIC (to its browser)
  Check if there is a breakpoint setting at the input lineNum then clear it.
  Else set a breakpoint at the input lineNum.
*/
Def bkptSetOrClear(self, bkptType, lineNum, selTxt | bkptId)
{ 
  if (bkptId := findBkptInDict(self, lineNum)) then
    ^removeBkptDesc(self, bkptId);
  else
    /* Always use 1 as the column of the first statement */
    ^setBkpt(self, bkptType, lineNum, 1, selTxt);
  endif;  
}
!!

/* 3/9/1994 14:52 */
Def clearBkptDict(self)
{ 
  if (bkptDict) then
    clear(bkptDict);
  endif;  
}
!!

/* 7/14/1992 15:06 - PUBLIC (to ancestor) */
Def setModuleTotalLines(self, numLines)
{
  if moduleInfo then
    setSourceLocations(moduleInfo, rememberedLocations);
    setSourceHighestChunk(moduleInfo, highestChunkSeen);
    setTotalSourceLines(moduleInfo, numLines, numLines);
  endif;
}
!!

/* 3/29/1992 13:58 - PUBLIC
  Disable breakpoint that matched with the 
  #(lineNum & (StartChar < colNum < EndChar).
  This also support Statement breakpoint.
*/
Def disableStmtBkpt(self, lineNum, colNum, txtStr | bkptInfo, mapLineNum )
{
  /* Map selected Line to actual symbolic line */
  mapLineNum := mapLine(self, lineNum, nil);

  /* Do associate to disable breakpoint */
  assocsDo (bkptDict,
    {using(bkpt)
      /* if mapLineNum == [bkptDescID->#(lineNum, colorTag)] then do it */
      if (mapLineNum = value(bkpt)[0]) then
        if (colNum > 0) then /* Request to remove a statement breakpoint */
          if not(bkptInfo := getSourceBkpt(HLBreakLibClass$Inst, key(bkpt))) 
            ^nil;
          endif;
          /* NOTE: 
          ** bkptInfo = #(state, life, module, lineNum, colStart, colEnd) 
          */
          if between(colNum, bkptInfo[4], bkptInfo[5]) then
            ^disableBkpt(HLBreakLibClass$Inst, key(bkpt));
          endif;
        else
          /* disable line breakpoint */
          ^disableBkpt(HLBreakLibClass$Inst, key(bkpt));
        endif;
      endif;
    });
  /* Can't find breakpoint */
  reportBkptError(self, SRC_BKPT_DISABLE_BKPT);
  ^nil;
}
!!

/* 3/29/1992 13:58 - PUBLIC
  Enable breakpoint that matched with the 
  #(lineNum & (StartChar < colNum < EndChar).
  This also support Statement breakpoint.
*/
Def enableStmtBkpt(self, lineNum, colNum, txtStr | bkptInfo, mapLineNum)
{
  /* Map selected Line to actual symbolic line */
  mapLineNum := mapLine(self, lineNum, nil);
  /* Do associate to enable breakpoint */
  assocsDo (bkptDict,
    {using(bkpt)
      /* if mapLineNum == [bkptDescID->#(lineNum, colorTag)] then do it */
      if (mapLineNum = value(bkpt)[0]) then
        if (colNum > 0) then /* Request to remove a statement breakpoint */
          if not(bkptInfo := getSourceBkpt(HLBreakLibClass$Inst, key(bkpt)))
            ^nil; /* Can't get bkptInfo */
          endif;
          /* NOTE: 
          ** bkptInfo = #(state, life, module, lineNum, colStart, colEnd) 
          */
          if between(colNum, bkptInfo[4], bkptInfo[5]) then
            ^enableBkpt(HLBreakLibClass$Inst, key(bkpt));
          endif;
        else
          /* Enable line breakpoint */
          ^enableBkpt(HLBreakLibClass$Inst, key(bkpt));
        endif;
      endif;
    });
  /* Can't find breakpoint */
  reportBkptError(self, SRC_BKPT_ENABLE_BKPT);
  ^nil;
}
!!

/* 3/29/1992 13:58 - PUBLIC
  Remove breakpoint that matched with the #(lineNum & (StartChar < colNum < EndChar).
  This also support Statement breakpoint.
*/
Def clearStmtBkpt(self, lineNum, colNum, txtStr | bkptInfo, mapLineNum )
{
  /* Map selected Line to actual symbolic line */
  mapLineNum := mapLine(self, lineNum, nil);

  /* Do associate to remove breakpoint */
  assocsDo (bkptDict,
    {using(bkpt)
      /* if mapLineNum == [bkptDescID->#(lineNum, colorTag)] then remove it */
      if (mapLineNum = value(bkpt)[0]) then
        if (colNum > 0) then /* Request to remove a statement breakpoint */
          if not(bkptInfo := getSourceBkpt(HLBreakLibClass$Inst, key(bkpt)))
            ^nil;
          endif;
          /* NOTE: bkptInfo = 
          **   #(state, life, module, lineNum, colStart, colEnd) 
          */
          if between(colNum, bkptInfo[4], bkptInfo[5]) then
            ^removeBkptDesc(self, key(bkpt));
          endif;
        else
          /* Remove line breakpoint */
          ^removeBkptDesc(self, key(bkpt));
        endif;
      endif;
    });
  /* Can't find breakpoint */
  reportBkptError(self, SRC_BKPT_CLR_BKPT);
  ^nil;
}
!!

/* 7/17/1992 14:53 PRIVATE
  return the address range descriptor of module.
  NOTES: Caller must clean up the return address descriptor when done.
*/
Def addressRange(self)
{
  if not(moduleInfo) then
    ^nil;
  endif;
  ^duplicateAddress(AddressLibClass$Inst, addressRange(moduleInfo));
}
!!

/* 3/25/1992 11:30 - PUBLIC */
Def bkptDict(self)
{
  ^bkptDict;
}
!!

/* 06/20/92 - PUBLIC
  Return the callersCache - last position the caller had remembered.
*/
Def callersCache(self)
{
  ^callersCache;
}
!!

/* 06/20/92 - PUBLIC
  Close self.
*/
Def close(self)
{
  clearBkptDict(self);
  currentExecPoint := nil;
  cursorLinked     := nil;
  close(self:ancestor);
  if moduleInfo  then
    setSourceLocations(moduleInfo, rememberedLocations);
    setSourceHighestChunk(moduleInfo, highestChunkSeen);
    setTotalSourceLines(moduleInfo, totalLinesGuess, totalLinesExact);
  endif;
}
 !!

/* 3/24/1992 14:11 - PUBLIC
  Destroy object and its data components.
*/
Def destroyDataObj(self)
{
  if not(closed?) then close(self); endif;
  callersCache := nil;
  /* NOTES: do not remove breakpoints - free Actor memory */
  bkptDict     := nil; 
  moduleInfo   := nil;
  modulePath   := nil;
}
!!

/* 4/6/1992 21:43 - PUBLIC
  Find a breakpoint in self dictionary. return breakpoint ID or nil;
     NOTES: bkptDict = [bkptID->#(lineNum, colorTag)],...
*/
Def findBkptInDict(self, lineNum)
{
  assocsDo(bkptDict,
    {using(bkpt)
      if (value(bkpt)[0] = lineNum) then
        ^key(bkpt);
      endif;
     });
  ^nil;
}
!!

/* 3/25/1992 11:36 - PUBLIC
  Check and see if the input lineNum is a special lines
  (e.g. Breakkpoints setting, and so on).
*/
Def findSpecialLine(self, lineNum | bkptId)
{
  if (size(bkptDict) > 0) then
    /* NOTES: bkptDict = ([bkptDescId->#(lineNum, colorTag)], ...) */
    if (bkptId := findBkptInDict(self, lineNum)) then
      ^(bkptDict[bkptId][1]); /* return the ColorTag */
    endif;
  endif;
  ^nil;
}
!!

/* 06/20/92 - PUBLIC
   If aFile has been reOpen()'ed, cached info may be stale.  The user
   can call this function to flush cached info, or not call it to keep
   cached info.
   NOTES: caller should flush its own callersCache.
*/
Def flushCachedMemory(self)
{
  flushCachedMemory( self:ancestor );
}
!!

/* 4/2/1992 14:41 - PUBLIC
  Get the current program counter and set its corresponding line in the source.
  current
  NOTES: the currentExecPoint is a lineNumber
*/
Def getCurrentExecPoint(self | pcAddr, addrInfo)
{
  if moduleDescriptor(self) cand 
     (pcAddr := getProgramCounter(CpuLibClass$Inst)) then
    /* Map the PC address to line number and module */
    if (addrInfo := addr2lineAndModule(SymbolLibClass$Inst, pcAddr)) then
      /* NOTES: 
      ** addrInfo = #(moduleDesc, moduleAddrDesc, lineNum, colNum, 
      **              lineAddrDesc, lineNumDesc) 
      */
      destroyAddress(AddressLibClass$Inst, addrInfo[1]);
      destroyAddress(AddressLibClass$Inst, addrInfo[4]);
      /* If they are the same module then set current execute point */
      if (addrInfo[0] = moduleDescriptor(moduleInfo)) then
        currentExecPoint := addrInfo[2];
      else
        currentExecPoint := nil;
      endif;
    endif;
    /* Get rid of the unused address */
    destroyAddress(AddressLibClass$Inst, pcAddr);
  endif;
  /* Return either the old or new current execution line */
  ^currentExecPoint;
}
!!

/* 3/24/1992 14:08 - PRIVATE
  initialize a VirtSmartFileObject
*/
Def init(self, newModuleInfo)
{
  /* Allocate holder for breakpoints */
  bkptDict := new(Dictionary, 2);
  clear(bkptDict);
  moduleInfo := newModuleInfo;
  if not(modulePath := filePath(newModuleInfo))
    ^nil;
  endif;  
  languageName := languageName(newModuleInfo);
  currentExecPoint := nil;
  ^init(self:ancestor, newModuleInfo);
}
!!

/* 06/20/92 - PUBLIC
  Return the moduleDescriptor of self.
*/
Def moduleDescriptor(self)
{
  if not(moduleInfo) then
    ^nil;
  endif;
  ^moduleDescriptor(moduleInfo);
}
!!

/* 7/22/1992 9:11 - PUBLIC
  Return the moduleInfo of self.
*/
Def moduleInfo(self)
{
  ^moduleInfo;
}!!

/* 06/20/92 - PUBLIC
  Open the text file of the module, then find out about it
*/
Def openFile(self, aFileName | newObj)
{
  if (newObj := openFile(self:ancestor, aFileName )) <> self then
    ^newObj /* error case */
  endif;
  getCurrentExecPoint(self);
  rebuildBkptDict(self);
}
!!

/* 3/25/1992 13:21 - PUBLIC
  Rebuild bkptDict respond to event notification.
     NOTES: bkptDict = [bkptID->#(sourceLineNum, colorTag),...]
*/
Def rebuildBkptDict(self | bkptInfo, bkptResult, loopBkpt )
{
  if not(moduleInfo) then
    displayFormattedError(ErrorTextLibClass$Inst,
       ER_INVALID_DATA_OBJECT, FORCE_POPUP, nil, nil, nil);
    ^nil;
  endif;

  clear(bkptDict);
  loopBkpt := nil; /* initial starting point for the loop */
  loop
  while (bkptResult := getNextSourceBkpt(HLBreakLibClass$Inst,
                                         moduleDescriptor(moduleInfo), 
                                         loopBkpt))
    begin
      /* NOTES: bkptResult = #(loopDesc, bkptDesc) */
      loopBkpt := bkptResult[0];
      /* Get breakpoint info to add to dictionary */
      if bkptInfo := getSourceBkpt(HLBreakLibClass$Inst, bkptResult[1]) then
        /* NOTES: 
        ** bkptInfo = #(state, life, module, lineNum, colStart, colEnd) 
        */
        add(bkptDict, bkptResult[1],
          tuple(bkptInfo[3], getBkptColor(self, bkptInfo[0], bkptInfo[1])) );
      endif;
  endLoop;
  ^size(bkptDict);
}
!!

/* 3/29/1992 13:44 - PUBLIC
  Remove all breakpoints in bkptDict of self.
*/
Def removeAllBkpt(self)
{
  if size(bkptDict) > 0 then
    keysDo (bkptDict,
      {using(bkptId)
        removeBkpt(HLBreakLibClass$Inst, bkptId);
    });
    clear(bkptDict);
  endif;
}
!!

/* 3/29/1992 17:00 - PRIVATE */
Def removeBkptDesc(self, bkptDescId)
{
  remove(bkptDict, bkptDescId);
  ^removeBkpt(HLBreakLibClass$Inst, bkptDescId);
}
!!

/* 06/20/92 - PUBLIC
   Reopen a previously opened file.
*/
Def reOpen(self, ignoreArg)
{
  if reOpen(self:ancestor, ignoreArg) then
    if moduleDescriptor(self) then
      getCurrentExecPoint(self);
      rebuildBkptDict(self);
      ^self;
    endif;  
  endif;
  ^nil;
}!!

/* 06/20/92 - PUBLIC
   Look for text in file starting at line startLine, column 0.
   If not found, return nil, else return #(line, column).
    NOTES: getTxtFunc is a block context from parent browser.
    RETURN:  #((startLine + line-offset) , char-offset)
*/
Def searchText(self, searchText, startLine, getTxtFunc | lineNum, chunk, pointPos)
{
  chunk := lineNum := startLine;
  loop while chunk 
  begin
    /* Check Abort */
    if  queryAbort(TaskLibClass$Inst) then
      ^nil;
    endif;
    /* NOTES: chunk = #(startLineNum, textColl) or nil */
    if chunk := getChunk(self, chunkNumberFromLineNumber(self, lineNum)) then
      if (pointPos := findStringInChunk(self, chunk[1],
            searchText, (lineNum - chunk[0]), 0, getTxtFunc)) then
        ^tuple((chunk[0] + y(pointPos)), x(pointPos));
      endif ;
      lineNum := (chunk[0] + linesPerChunk);
    endif ;
   endLoop;
  ^nil
}
!!

/* 3/24/1992 14:19 - PUBLIC
  set a breakpoint let event update.
*/
Def setBkpt(self, bkptType, lineNum, colNum, txtStr)
{
  /* Begin to set a break point on the mapped selectLine */
  if not(setSourceBkpt(HLBreakLibClass$Inst,
      HLB_BKPT_STATE_ENABLED,                /* state */
      bkptType,                              /* life */
      moduleDescriptor(self),                /* moduleDesc */
      lineNum,                               /* lineNum */
      colNum,                                /* column */
      txtStr,                                /* text */   
      addressRange(self)                     /* AddressDesc */ 
      )) then
    ^nil;
  endif;
  /* Let Event Notification update the bkptDict */
  ^GOOD;
}
!!

/* 06/20/92 - PUBLIC */
Def setCallersCache(self, newCacheObject)
{
  ^(callersCache := newCacheObject);
}
!!

/* 06/20/92 - PUBLIC */
Def setModulePath(self, newPath)
{
  modulePath := asLowerCase(newPath);
  if sourceOutOfSynch?(LoaderLibClass$Inst, modulePath) then
    if not(reportOutOfSynch(moduleInfo)) then
      displayFormattedError(ErrorTextLibClass$Inst,
         ER_SOURCE_NEWER, FORCE_POPUP, nil, nil, nil);
    endif;
    modulePath := modulePath+"*";
    setReportOutOfSynch(moduleInfo, #true);
  endif;
}
!!

/* 7/21/1992 11:32 - PUBLIC (to its Parent)
  Set the current PC to the corresponding address of the input line.
  NOTES: return nil if failed.
*/
Def setPC(self, lineNum, colNum, ignoreArg2 | lineInfo)
{
  /* Map a source line number to its equivalent address */
  if not(lineInfo := getLineNumInfo(SymbolLibClass$Inst, moduleDescriptor(self), lineNum)) then
    ^nil;
  endif;
  /* NOTES: lineInfo = #(addrDesc, lineNum, colNum, lineNumDesc) */
  ^setProgramCounter(CpuLibClass$Inst, lineInfo[0]);
}
!!

/* 06/20/92 - PUBLIC */
Def virtObjTitle(self | len)
{
  /* Truncate the title to be less than 25 characters */
  if (len := size(modulePath)) > 25 then
     ^"..."+subString(modulePath, len - 25, len);
  endif;
  ^modulePath;
}
!!
