2019-08-23 11:56:54 -07:00
using System ;
2020-04-29 06:40:03 -07:00
using System.Collections ;
2019-08-23 11:56:54 -07:00
using System.Text ;
using System.Threading.Tasks ;
using IDE.ui ;
using System.Diagnostics ;
2022-08-23 10:45:57 -07:00
using System.IO ;
2019-08-23 11:56:54 -07:00
namespace IDE
{
public class TrackedTextElement
{
public int mRefCount = 1 ;
public bool mIsDead ;
public String mFileName ~ delete _ ;
public int32 mLineNum ;
public int32 mColumn ;
public bool mSnapToLineStart = true ;
public int32 mMoveIdx ; // Increments when we manually move an element (ie: a history location updating because the user types near the previous history location)
public virtual void Move ( int wantLineNum , int wantColumn )
{
mLineNum = ( int32 ) wantLineNum ;
mColumn = ( int32 ) wantColumn ;
}
public virtual void Kill ( )
{
mIsDead = true ;
Deref ( ) ;
}
public void AddRef ( )
{
mRefCount + + ;
}
public void Deref ( )
{
if ( - - mRefCount = = 0 )
delete this ;
}
public ~ this ( )
{
Debug . Assert ( mRefCount = = 0 ) ;
}
}
public class Bookmark : TrackedTextElement
2022-06-08 20:11:39 +02:00
{
public String mTitle ~ delete _ ;
2019-08-23 11:56:54 -07:00
public String mNotes ~ delete _ ;
2022-06-08 20:11:39 +02:00
public bool mIsDisabled ;
2022-06-09 12:25:48 +02:00
public BookmarkFolder mFolder ;
2022-08-13 19:24:17 +02:00
internal int IndexInFolder = > mFolder . mBookmarkList . IndexOf ( this ) ;
2019-08-23 11:56:54 -07:00
}
2022-06-09 12:25:48 +02:00
public class BookmarkFolder
{
/// The title of the bookmark-folder that will be visible in the bookmark-panel
public String mTitle ~ delete _ ;
2022-08-13 19:24:17 +02:00
public List < Bookmark > mBookmarkList = new List < Bookmark > ( ) ~ delete _ ;
2022-06-09 12:25:48 +02:00
/// Gets or Sets whether every bookmark in this folder is disabled or not.
2022-08-23 10:45:57 -07:00
public bool AreAllDisabled
2022-06-09 12:25:48 +02:00
{
get
{
for ( var bookmark in mBookmarkList )
if ( ! bookmark . mIsDisabled )
return false ;
return true ;
}
set
{
for ( var bookmark in mBookmarkList )
bookmark . mIsDisabled = value ;
2022-08-13 19:24:17 +02:00
gApp . mBookmarkManager . BookmarksChanged ( ) ;
2022-06-09 12:25:48 +02:00
}
}
2022-08-23 10:45:57 -07:00
public bool AreAllEnabled
{
get
{
for ( var bookmark in mBookmarkList )
if ( bookmark . mIsDisabled )
return false ;
return true ;
}
set
{
for ( var bookmark in mBookmarkList )
bookmark . mIsDisabled = ! value ;
gApp . mBookmarkManager . BookmarksChanged ( ) ;
}
}
2022-06-09 12:25:48 +02:00
/// Adds the given bookmark to this folder. If needed, removes it from its old folder.
2022-06-11 16:45:16 +02:00
internal void AddBookmark ( Bookmark bookmark )
2022-06-09 12:25:48 +02:00
{
if ( bookmark . mFolder ! = null )
{
bookmark . mFolder . mBookmarkList . Remove ( bookmark ) ;
}
mBookmarkList . Add ( bookmark ) ;
bookmark . mFolder = this ;
}
2022-08-13 19:24:17 +02:00
internal int Index = > gApp . mBookmarkManager . mBookmarkFolders . IndexOf ( this ) ;
2022-06-09 12:25:48 +02:00
}
2022-08-13 19:24:17 +02:00
using internal Bookmark ;
using internal BookmarkFolder ;
2022-06-09 12:25:48 +02:00
public class BookmarkManager
{
public BookmarkFolder mRootFolder = new . ( ) ;
2022-08-13 21:20:04 +02:00
public List < BookmarkFolder > mBookmarkFolders = new . ( ) { mRootFolder } ~
{
while ( ! _ . IsEmpty )
{
DeleteFolder ( _ . Back ) ;
}
delete _ ;
} ;
2022-06-09 12:25:48 +02:00
2022-08-13 19:24:17 +02:00
/// Occurs when a bookmark/folder is added, removed or moved.
public Event < Action > BookmarksChanged ~ _ . Dispose ( ) ;
public delegate void MovedToBookmarkEventHandler ( Bookmark bookmark ) ;
// Occurs when the user jumped to a bookmark.
public Event < MovedToBookmarkEventHandler > MovedToBookmark ~ _ . Dispose ( ) ;
2022-06-11 16:45:16 +02:00
2022-06-09 12:25:48 +02:00
private int mBookmarkCount ;
/// Number of bookmarks created, used to generate the names.
2022-08-23 10:45:57 -07:00
private int32 sBookmarkId ;
2022-06-09 12:25:48 +02:00
/// Number of folders created, used to generate the names.
2022-08-23 10:45:57 -07:00
private int32 sFolderId ;
2022-06-09 12:25:48 +02:00
/// Gets or sets whether all bookmarks are disabled or not.
2022-06-08 20:11:39 +02:00
public bool AllBookmarksDisabled
{
get
{
2022-06-09 12:25:48 +02:00
for ( var folder in mBookmarkFolders )
2022-06-08 20:11:39 +02:00
{
2022-08-23 10:45:57 -07:00
if ( ! folder . AreAllDisabled )
2022-06-08 20:11:39 +02:00
return false ;
}
return true ;
}
2022-06-09 12:25:48 +02:00
set
{
for ( var folder in mBookmarkFolders )
{
2022-08-23 10:45:57 -07:00
folder . AreAllDisabled = value ;
2022-06-09 12:25:48 +02:00
}
2022-08-13 19:24:17 +02:00
BookmarksChanged ( ) ;
2022-06-09 12:25:48 +02:00
}
}
2022-08-13 19:24:17 +02:00
/// Gets the currently selected bookmark or null, if no bookmark is selected.
public Bookmark CurrentBookmark
{
get ;
private set ;
}
public BookmarkFolder CurrentFolder = > CurrentBookmark ? . mFolder ;
2022-06-09 12:25:48 +02:00
/ * *
* Creates a new bookmark folder
* @param title The title of the bookmark
* @returns the newly created BookmarkFolder
* /
public BookmarkFolder CreateFolder ( String title = null )
{
BookmarkFolder folder = new . ( ) ;
if ( title = = null )
2022-08-23 10:45:57 -07:00
folder . mTitle = new $"Folder {++sFolderId}" ;
2022-06-09 12:25:48 +02:00
else
folder . mTitle = new String ( title ) ;
2022-08-13 19:24:17 +02:00
mBookmarkFolders . Add ( folder ) ;
2022-06-09 12:25:48 +02:00
2022-08-13 19:24:17 +02:00
BookmarksChanged ( ) ;
2022-06-09 12:25:48 +02:00
return folder ;
2022-06-08 20:11:39 +02:00
}
2022-06-09 12:25:48 +02:00
/// Deletes the given bookmark folder and all bookmarks inside it.
public void DeleteFolder ( BookmarkFolder folder )
2022-06-08 20:11:39 +02:00
{
2022-08-13 19:24:17 +02:00
if ( folder = = CurrentFolder )
{
// the current bookmark will be deleted, so we have to find another one
int folderIdx = mBookmarkFolders . IndexOf ( folder ) ;
int newFolderIdx = folderIdx ;
BookmarkFolder newFolder = null ;
repeat
{
newFolderIdx - - ;
if ( newFolderIdx < 0 )
{
newFolderIdx = mBookmarkFolders . Count - 1 ;
}
if ( mBookmarkFolders [ newFolderIdx ] . mBookmarkList . Count ! = 0 )
{
newFolder = mBookmarkFolders [ newFolderIdx ] ;
break ;
}
}
// Break when we reach start
while ( ( folderIdx ! = newFolderIdx ) ) ;
if ( newFolder ! = null )
{
Debug . Assert ( newFolder . mBookmarkList . Count ! = 0 ) ;
CurrentBookmark = newFolder . mBookmarkList [ 0 ] ;
}
}
while ( ! folder . mBookmarkList . IsEmpty )
{
gApp . mBookmarkManager . DeleteBookmark ( folder . mBookmarkList . Back ) ;
}
2022-06-09 12:25:48 +02:00
mBookmarkFolders . Remove ( folder ) ;
delete folder ;
2022-08-23 06:46:21 -07:00
if ( gApp . mDebugger ? . mBreakpointsChangedDelegate . HasListeners = = true )
2022-08-13 19:24:17 +02:00
gApp . mDebugger . mBreakpointsChangedDelegate ( ) ;
2022-06-09 12:25:48 +02:00
2022-08-13 19:24:17 +02:00
BookmarksChanged ( ) ;
2022-06-09 12:25:48 +02:00
}
public Bookmark CreateBookmark ( String fileName , int wantLineNum , int wantColumn , bool isDisabled = false , String title = null , BookmarkFolder folder = null )
{
var folder ;
folder = folder ? ? mRootFolder ;
2019-08-23 11:56:54 -07:00
Bookmark bookmark = new Bookmark ( ) ;
bookmark . mFileName = new String ( fileName ) ;
bookmark . mLineNum = ( int32 ) wantLineNum ;
bookmark . mColumn = ( int32 ) wantColumn ;
2022-06-08 20:11:39 +02:00
if ( title = = null )
2022-08-23 10:45:57 -07:00
{
String baseName = Path . GetFileNameWithoutExtension ( fileName , . . scope . ( ) ) ;
if ( IDEApp . IsSourceCode ( fileName ) )
{
var bfSystem = IDEApp . sApp . mBfResolveSystem ;
if ( bfSystem ! = null )
{
String content = scope . ( ) ;
gApp . LoadTextFile ( fileName , content ) . IgnoreError ( ) ;
var parser = bfSystem . CreateEmptyParser ( null ) ;
defer delete parser ;
parser . SetSource ( content , fileName , - 1 ) ;
var passInstance = bfSystem . CreatePassInstance ( ) ;
defer delete passInstance ;
2022-08-23 11:25:36 -07:00
parser . Parse ( passInstance , ! IDEApp . IsBeefFile ( fileName ) ) ;
2022-08-23 10:45:57 -07:00
parser . Reduce ( passInstance ) ;
String name = parser . GetLocationName ( wantLineNum , wantColumn , . . scope . ( ) ) ;
if ( ! name . IsEmpty )
bookmark . mTitle = new $"#{++sBookmarkId} {name}" ;
}
}
if ( bookmark . mTitle = = null )
{
bookmark . mTitle = new $"#{++sBookmarkId} {baseName}" ;
}
}
2022-06-08 20:11:39 +02:00
else
bookmark . mTitle = new String ( title ) ;
bookmark . mIsDisabled = isDisabled ;
2022-06-09 12:25:48 +02:00
2022-06-11 16:45:16 +02:00
folder . [ Friend ] AddBookmark ( bookmark ) ;
2022-06-08 20:11:39 +02:00
2022-08-13 19:24:17 +02:00
if ( gApp . mDebugger . mBreakpointsChangedDelegate . HasListeners )
gApp . mDebugger . mBreakpointsChangedDelegate ( ) ;
CurrentBookmark = bookmark ;
BookmarksChanged ( ) ;
2022-06-08 20:11:39 +02:00
2022-06-09 12:25:48 +02:00
mBookmarkCount + + ;
2019-08-23 11:56:54 -07:00
return bookmark ;
}
2022-08-13 19:24:17 +02:00
public void DeleteBookmark ( Bookmark bookmark )
{
bool deletedCurrentBookmark = false ;
Bookmark newCurrentBookmark = null ;
if ( bookmark = = CurrentBookmark )
{
deletedCurrentBookmark = true ;
// try to select a bookmark from the current folder
newCurrentBookmark = FindPrevBookmark ( true ) ;
if ( newCurrentBookmark = = null | | newCurrentBookmark = = CurrentBookmark )
{
// Current folder is empty, select from previous folder
newCurrentBookmark = FindPrevBookmark ( false ) ;
if ( newCurrentBookmark = = CurrentBookmark )
{
// We have to make sure, that we don't select the current bookmark
newCurrentBookmark = null ;
}
}
}
BookmarkFolder folder = bookmark . mFolder ;
folder . mBookmarkList . Remove ( bookmark ) ;
bookmark . Kill ( ) ;
if ( deletedCurrentBookmark )
CurrentBookmark = newCurrentBookmark ;
if ( gApp . mDebugger . mBreakpointsChangedDelegate . HasListeners )
gApp . mDebugger . mBreakpointsChangedDelegate ( ) ;
BookmarksChanged ( ) ;
mBookmarkCount - - ;
}
enum Placement
{
Before ,
After
}
2022-06-09 12:25:48 +02:00
/ * * Moves the bookmark to the specified folder .
* @param bookmark The bookmark to move .
* @param folder The folder to which the bookmark will be moved .
* @param insertBefore If null the bookmark will be added at the end of the folder . Otherwise it will be inserted before the specified bookmark .
* /
2022-08-13 19:24:17 +02:00
public void MoveBookmarkToFolder ( Bookmark bookmark , BookmarkFolder targetFolder , Placement place = . Before , Bookmark targetBookmark = null )
2022-06-09 12:25:48 +02:00
{
if ( bookmark . mFolder ! = null )
{
bookmark . mFolder . mBookmarkList . Remove ( bookmark ) ;
}
2022-08-13 19:24:17 +02:00
if ( targetBookmark = = null )
targetFolder . mBookmarkList . Add ( bookmark ) ;
2022-06-09 12:25:48 +02:00
else
{
2022-08-13 19:24:17 +02:00
Debug . Assert ( targetFolder = = targetBookmark . mFolder , "Insert before must be in folder." ) ;
2022-06-09 12:25:48 +02:00
2022-08-13 19:24:17 +02:00
int index = targetFolder . mBookmarkList . IndexOf ( targetBookmark ) ;
if ( place = = . After )
index + + ;
2022-06-09 12:25:48 +02:00
2022-08-13 19:24:17 +02:00
targetFolder . mBookmarkList . Insert ( index , bookmark ) ;
2022-06-09 12:25:48 +02:00
}
2022-08-13 19:24:17 +02:00
bookmark . mFolder = targetFolder ;
2022-06-09 12:25:48 +02:00
2022-08-13 19:24:17 +02:00
BookmarksChanged ( ) ;
2022-06-09 12:25:48 +02:00
}
2022-08-13 19:24:17 +02:00
/ * * Moves the given folder in front of or behind the given target - folder .
* @param folder The folder to move .
* @param place Specifies whether folder will be placed in front of or behind target .
* @param target The folder relative to which folder will be placed .
* /
2022-06-09 12:25:48 +02:00
public void MoveFolder ( BookmarkFolder folder , Placement place = . After , BookmarkFolder target = null )
{
if ( folder = = target )
return ;
if ( mBookmarkFolders . Contains ( folder ) )
mBookmarkFolders . Remove ( folder ) ;
if ( target = = null )
{
mBookmarkFolders . Add ( folder ) ;
}
else
{
int index = mBookmarkFolders . IndexOf ( target ) ;
if ( place = = . After )
index + + ;
mBookmarkFolders . Insert ( index , folder ) ;
}
2022-08-13 19:24:17 +02:00
BookmarksChanged ( ) ;
2022-06-09 12:25:48 +02:00
}
2022-08-13 19:24:17 +02:00
/// Deletes all bookmarks and bookmark-folders.
2019-08-23 11:56:54 -07:00
public void Clear ( )
{
2022-06-09 12:25:48 +02:00
for ( var folder in mBookmarkFolders )
{
for ( var bookmark in folder . mBookmarkList )
bookmark . Kill ( ) ;
folder . mBookmarkList . Clear ( ) ;
}
ClearAndDeleteItems ! ( mBookmarkFolders ) ;
mRootFolder = new BookmarkFolder ( ) ;
mBookmarkFolders . Add ( mRootFolder ) ;
2022-06-08 20:11:39 +02:00
2022-08-13 19:24:17 +02:00
if ( gApp . mDebugger . mBreakpointsChangedDelegate . HasListeners )
gApp . mDebugger . mBreakpointsChangedDelegate ( ) ;
mBookmarkCount = 0 ;
2023-06-08 10:05:29 -04:00
CurrentBookmark = null ;
2022-08-13 19:24:17 +02:00
BookmarksChanged ( ) ;
2019-08-23 11:56:54 -07:00
}
2022-08-13 19:24:17 +02:00
/ * * Finds and returns the previous bookmark relative to CurrentBookmark .
* @param currentFolderOnly If set to true , only the current folder will be searched . Otherwise all folders will be searched .
* @returns The previous bookmark . If CurrentBookmark is the only bookmark , CurrentBookmark will be returned . Null , if there are no bookmarks .
* /
private Bookmark FindPrevBookmark ( bool currentFolderOnly )
{
2022-06-09 12:25:48 +02:00
if ( mBookmarkCount = = 0 )
2022-08-13 19:24:17 +02:00
return null ;
2019-08-23 11:56:54 -07:00
2022-08-13 19:24:17 +02:00
int currentFolderIdx = CurrentFolder . Index ;
int currentBookmarkIdx = CurrentBookmark . IndexInFolder ;
2022-06-08 20:11:39 +02:00
2022-06-09 18:41:02 +02:00
Bookmark prevBookmark = null ;
2022-08-13 19:24:17 +02:00
int newFolderIndex = currentFolderIdx ;
int newBmIndex = currentBookmarkIdx ;
2022-06-08 20:11:39 +02:00
repeat
{
2022-08-13 19:24:17 +02:00
newBmIndex - - ;
2022-06-09 12:25:48 +02:00
2022-08-13 19:24:17 +02:00
if ( newBmIndex < 0 )
2022-06-09 12:25:48 +02:00
{
if ( ! currentFolderOnly )
{
2022-08-13 19:24:17 +02:00
newFolderIndex - - ;
2022-06-09 18:41:02 +02:00
2022-08-13 19:24:17 +02:00
if ( newFolderIndex < 0 )
2022-06-09 12:25:48 +02:00
{
2022-06-09 18:41:02 +02:00
// wrap to last folder
2022-08-13 19:24:17 +02:00
newFolderIndex = ( int32 ) mBookmarkFolders . Count - 1 ;
2022-06-09 12:25:48 +02:00
}
}
2022-06-09 18:41:02 +02:00
// Select last bookmark in current folder
2022-08-13 19:24:17 +02:00
newBmIndex = ( int32 ) mBookmarkFolders [ newFolderIndex ] . mBookmarkList . Count - 1 ;
2022-06-09 12:25:48 +02:00
}
2022-08-13 19:24:17 +02:00
if ( newBmIndex > = 0 )
prevBookmark = mBookmarkFolders [ newFolderIndex ] . mBookmarkList [ newBmIndex ] ;
2022-06-09 12:25:48 +02:00
else
2022-06-09 18:41:02 +02:00
prevBookmark = null ;
2022-06-08 20:11:39 +02:00
}
// skip disabled bookmarks, stop when we reach starting point
2022-08-13 19:24:17 +02:00
while ( ( prevBookmark = = null | | prevBookmark . mIsDisabled ) & & ( ( currentFolderIdx ! = newFolderIndex ) | | ( currentBookmarkIdx ! = newBmIndex ) & & newBmIndex ! = - 1 ) ) ;
return prevBookmark ;
}
/ * * Jumps to the previous bookmark .
* @param currentFolderOnly If true , only the current folder will be searched . Otherwise all folders will be searched .
* /
public void PrevBookmark ( bool currentFolderOnly = false )
{
Bookmark prevBookmark = FindPrevBookmark ( currentFolderOnly ) ;
2019-08-23 11:56:54 -07:00
2022-06-09 18:41:02 +02:00
// If prevBookmark is disabled no bookmark is enabled.
if ( prevBookmark ! = null & & ! prevBookmark . mIsDisabled )
GotoBookmark ( prevBookmark ) ;
2019-08-23 11:56:54 -07:00
}
2022-08-13 19:24:17 +02:00
/ * Jumps to the next bookmark .
* @param currentFolderOnly If true , only the current folder will be searched . Otherwise all folders will be searched .
* /
2022-06-09 12:25:48 +02:00
public void NextBookmark ( bool currentFolderOnly = false )
2019-08-23 11:56:54 -07:00
{
2022-06-09 12:25:48 +02:00
if ( mBookmarkCount = = 0 )
return ;
2019-08-23 11:56:54 -07:00
2022-08-13 19:24:17 +02:00
int currentFolderIdx = CurrentFolder . Index ;
int currentBookmarkIdx = CurrentBookmark . IndexInFolder ;
2022-06-08 20:11:39 +02:00
2022-06-09 12:25:48 +02:00
Bookmark nextBookmark = null ;
2022-08-13 19:24:17 +02:00
int newFolderIndex = currentFolderIdx ;
int newBmIndex = currentBookmarkIdx ;
2022-06-08 20:11:39 +02:00
repeat
{
2022-08-13 19:24:17 +02:00
newBmIndex + + ;
2022-06-09 12:25:48 +02:00
2022-08-13 19:24:17 +02:00
if ( newBmIndex > = mBookmarkFolders [ newFolderIndex ] . mBookmarkList . Count )
2022-06-09 12:25:48 +02:00
{
if ( ! currentFolderOnly )
{
2022-08-13 19:24:17 +02:00
newFolderIndex + + ;
if ( newFolderIndex > = mBookmarkFolders . Count )
2022-06-09 12:25:48 +02:00
{
2022-06-09 18:41:02 +02:00
// wrap to first folder
2022-08-13 19:24:17 +02:00
newFolderIndex = 0 ;
2022-06-09 12:25:48 +02:00
}
}
2022-06-09 18:41:02 +02:00
// Select first bookmark in current folder (or -1 if there is no bookmark)
2022-08-13 19:24:17 +02:00
newBmIndex = mBookmarkFolders [ newFolderIndex ] . mBookmarkList . IsEmpty ? - 1 : 0 ;
2022-06-09 12:25:48 +02:00
}
2022-06-08 20:11:39 +02:00
2022-08-13 19:24:17 +02:00
if ( newBmIndex > = 0 )
nextBookmark = mBookmarkFolders [ newFolderIndex ] . mBookmarkList [ newBmIndex ] ;
2022-06-09 12:25:48 +02:00
else
nextBookmark = null ;
2022-06-08 20:11:39 +02:00
}
// skip disabled bookmarks, stop when we reach starting point
2022-08-13 19:24:17 +02:00
while ( ( nextBookmark = = null | | nextBookmark . mIsDisabled ) & & ( ( currentFolderIdx ! = newFolderIndex ) | | ( currentBookmarkIdx ! = newBmIndex ) & & newBmIndex ! = - 1 ) ) ;
2022-06-08 20:11:39 +02:00
2022-06-09 12:25:48 +02:00
// If nextBookmark is disabled no bookmark is enabled.
if ( nextBookmark ! = null & & ! nextBookmark . mIsDisabled )
GotoBookmark ( nextBookmark ) ;
2019-08-23 11:56:54 -07:00
}
2022-06-08 20:11:39 +02:00
2022-08-13 19:24:17 +02:00
/// Moves the cursor to the given bookmark.
2022-06-08 20:11:39 +02:00
public void GotoBookmark ( Bookmark bookmark )
{
2022-08-13 19:24:17 +02:00
CurrentBookmark = bookmark ;
2022-06-09 12:25:48 +02:00
2022-06-08 20:11:39 +02:00
gApp . ShowSourceFileLocation ( bookmark . mFileName , - 1 , - 1 , bookmark . mLineNum , bookmark . mColumn , LocatorType . Smart ) ;
2022-08-13 19:24:17 +02:00
MovedToBookmark ( bookmark ) ;
2022-06-08 20:11:39 +02:00
}
2022-08-23 10:45:57 -07:00
public void RecalcCurId ( )
{
sBookmarkId = 0 ;
sFolderId = 0 ;
for ( var folder in mBookmarkFolders )
{
if ( folder . mTitle ? . StartsWith ( "Folder " ) = = true )
{
if ( int32 curId = int32 . Parse ( folder . mTitle . Substring ( "Folder " . Length ) ) )
sFolderId = Math . Max ( sFolderId , curId ) ;
}
for ( var bookmark in folder . mBookmarkList )
{
if ( bookmark . mTitle . StartsWith ( "#" ) )
{
int spacePos = bookmark . mTitle . IndexOf ( ' ' ) ;
if ( spacePos ! = - 1 )
{
if ( int32 curId = int32 . Parse ( bookmark . mTitle . Substring ( 1 , spacePos - 1 ) ) )
sBookmarkId = Math . Max ( sBookmarkId , curId ) ;
}
}
}
}
}
2019-08-23 11:56:54 -07:00
}
}