**** icd  is an interactive cd command **** TABS 8 ***********************
*                                                                         *
*   I wrote this because I wanted to have a go at learning how to         *
* program in assembly plus sometimes I've wanted some way of checking     *
* what is on a disk before I cd to it, now I just type something like     *
* > icd df0:-^ and by pressing the RIGHT ALT key I can scroll through     *
* the directories on df0: until I find the directory I want and press     *
* RIGHT SHIFT to select it or if the one I want isn't there I press the   *
* LEFT ALT key to abort without changing directories and try again with   *
* df1: or put in the correct disk. The '-' must be directly after the     *
* name.                                                                   *
*                                                                         *
*   By using a period eg. > icd .^ it will scroll the same (by pressing   *
* the RIGHT ALT key) in the current directory. It can be used as a basic  *
* cd command eg. > icd df0: will change the current directory to df0:.    *
*                                                                         *
*   It doesn't handle wild cards and it can't be made resident.           *
*                                                                         *
*   On my system I've set it up with some alias such as :-                *
*  'alias 0- icd df0:-' and 'alias 1- icd df1:-'.                         *
*                                                                         *
*   By typing the program name on its' own or with a ? instructions will  *
* be printed to the screen. If someone changes its' name the              *
* instructions will always show the correct name i.e. the one it was      *
* changed to.                                                             *
*   I've left in the option to select by pressing RETURN even though      *
* this does produce a double prompt.                                      *
*                                                                         *
*Thanks go to Cedric Beust (Internet) for the C example of how to insert a*
*string where prompt can find it, and Robert Lang for helping to translate*
*that c example into assembler. Both examples are at the end of this code.*
***************************************************************************

****** FAULTS *********
* Giving double prompt when pressing RETURN to select directory., the same
* effect when scrolling is stopped by pressing the space bar and then
* recommencing by press return.
* Apart from that and looking like an unmade bed it works ok on my machine.
**********************
* I have a 2000 and 1.3 with 5 megs two 3.5 drives, one 5.25 IBM drive, a
* Janus hard drive and usualy a VD0:. I use AShell.
* I've assembled it with a68k, Blinked it with its' .obj and amiga.lib.
* Please forward any constructive comments or hints or fault reports to,
* B.Scott, 20/100 Lawler St. Subiaco, Western Australia, 6008.
**********************

	xref	_LVOOpenLibrary
	xref	_LVOCloseLibrary
	xref	_LVOAllocMem
	xref	_LVOFreeMem
	xref	_LVOWrite
	xref	_LVOOutput
	xref	_LVOCurrentDir
	xref	_LVOLock
	xref	_LVOUnLock
	xref	_LVOExamine
	xref	_LVOExNext

long             set 4
filename         set 108
comment          set 116
InfoBlock        set 0
fib_DiskKey      set InfoBlock	;0
InfoBlock        set InfoBlock+long
fib_DirEntryType set InfoBlock	;4
InfoBlock        set InfoBlock+long
fib_FileName     set InfoBlock	;8
InfoBlock        set InfoBlock+filename
fib_Protection   set InfoBlock	;116
InfoBlock        set InfoBlock+long
fib_EntryType    set InfoBlock	;120
InfoBlock        set InfoBlock+long
fib_Size         set InfoBlock	;124
InfoBlock        set InfoBlock+long
fib_NumBlocks    set InfoBlock	;128
InfoBlock        set InfoBlock+long
fib_Days         set InfoBlock	;132
InfoBlock        set InfoBlock+long
fib_Minute       set InfoBlock	;136
InfoBlock        set InfoBlock+long
fib_Tick         set InfoBlock	;140
InfoBlock        set InfoBlock+long
fib_Comment      set InfoBlock	;144
InfoBlock        set InfoBlock+comment
fib_SIZEOF       set InfoBlock	;260

OutHdlD	equr	d6
LockD	equr	d5
DirNameD	equr	d4
FibA	equr	a4

KEYPRESS	equ	$BFEC01	;keyboard hardware location
RALTup	equ	$34
RALTdown	equ	$35
LALTup	equ	$36
LALTdown	equ	$37
RSHIFT	equ	$3D
RTNup	equ	$76
RTNdown	equ	$77
LF	equ	$0A
SPACE	equ	$20
MINUS	equ	$2D
STOP	equ	$2E
QMARK	equ	$3F

WORKBUF	equ	512
EXECBASE	equ	4
MEMF_CLEAR	equ	$10000
MEMF_PUBLIC	equ	$01
	BRA	start

arpbase     dc.l    0	;Putting this lot up here instead of at the bottom
dosbase     dc.l    0	; of the code cut 52 bytes off the programs' size.
workbuf     dc.l    0
arglen      dc.l    0
            even
nildirs     dc.b    "No dirs!",$0A,0
            even
ndlgth      equ    *-nildirs
dosname     dc.b    'dos.library',0
            even
crntdir     dc.b    '',0
            even
keyb        dc.b    0
            even
browseflag  dc.b    0
            even
cdirflag    dc.b    0
            even
qmflag      dc.b    0
            even
noarp       dc.b    'You need '
            even
arp         dc.b    'arp.library',0
            even
	  dc.b	 ' V39+'
            even
lfnull      dc.b    $0A,0
            even
noarplth    equ     *-noarp
warn        dc.b    ' NOT FOUND!',$0A,0
            even
eg1         dc.b    ' is an interactive cd command',$0A,$0A,
            dc.b    'Some examples:..',$0A,$0A,0
            even
eg1a        dc.b  '> ',0
            even
eg2         dc.b  ' DF1:-'$09,'Scrolls DF1:',$0A
            dc.b  $09,$09,'(NOTE the ',$60,'-',$60,' directly',
            dc.b  ' after the name)',$0A,$0A,0
            even
eg3         dc.b  ' .'$09,'Scrolls current directory directories',
            dc.b  $0A,$0A,0
            even
eg4         dc.b  ' DF0:'$09,'Makes DF0: the current directory',$0A,$0A,
            dc.b  'RIGHT ALT scrolls through directory',
            dc.b  ' names',$0A,'RIGHT SHIFT selects the directory',$0A
            dc.b 'LEFT ALT ABORTS without changing current directory',
            dc.b $0A,0
           even
start:
	MOVEA.L	a0,a3		;Save a0 for arg check later
	move.l	d0,arglen	;Also save arg length
	MOVEA.L	EXECBASE,a6
	LEA	dosname(pc),a1
	JSR	_LVOOpenLibrary(a6)
	MOVE.L	d0,dosbase
	BEQ	quickqu

	MOVE.L	#fib_SIZEOF+WORKBUF,d0
	MOVE.L	#MEMF_CLEAR|MEMF_PUBLIC,d1
	JSR	_LVOAllocMem(a6)
	TST.L	d0
	BEQ	allocq
	MOVEA.L	d0,FibA		;Prepare a4 for FileInfoBlock.
	MOVEA.L	FibA,a5
	ADDA.L	#fib_SIZEOF,a5	; a5 = arg address
	MOVE.L	a5,workbuf	;Need this for path in browse mode

	MOVEA.L	dosbase(pc),a6
	JSR	_LVOOutput(a6)	;Setup CLI window as output for text
	MOVE.L	d0,OutHdlD	;Save output file handle.

	MOVEQ	#0,d0
	MOVE.L	#noarp,d2
	MOVE.L	#noarplth,d3
	LEA	arp(pc),a1	;'arp.library',0
	MOVEA.L	EXECBASE,a6
	JSR	-$228(A6)	;OpenLibrary
	MOVE.L	d0,arpbase
	BEQ	leave

	MOVEQ	#0,d1
	MOVEQ	#0,DirNameD
	MOVEQ	#fib_FileName,d7	;It's quicker to add registers
	CMPI.B	#LF,(a3)		;Check are there any args.
	BEQ.S	setqmflag	;Print instructions and quit

;xls:	CMPI.B	#SPACE,(a3)+	;Exclude leading spaces
;	BEQ.S	xls		; my debugger insists.
;	TST.B	-(a3)		;Set a3 back again. Loop adds one too many

	CMPI.B	#QMARK,(a3)
	BEQ.S	setqmflag	;Print instructions and quit
	CMPI.B	#STOP,(a3)	;Scroll current directory's dirs
	BEQ.S	setthisdirsflag	
arglop:
	MOVE.B	(a3)+,(a5)+	;One char at a time into workbuf.
	CMPI.B	#LF,(a3)	;Exclude the linefeed.
	BEQ.S	lok		;Try to make requested dir current.
	CMPI.B	#MINUS,(a3)	;Do they want browse mode
	BEQ.S	setbflag	;Try to make requested dir current.
	BRA.S	arglop		;While no linfeed, keep looping.

setqmflag:
	MOVE.B	#QMARK,qmflag	;Need to get lock to get
	BRA.S	lok		; path string for instructions
setbflag:
	CMPI.B	#LF,1(a3)		;Incase a directory name like
	BNE.S	arglop			; My-Dir is called for
	MOVE.B	#MINUS,browseflag	;To look into the dir arg requested
	BRA.S	lok			; with out being committed
setthisdirsflag:
	MOVE.B	#STOP,cdirflag	;For current dir browsing

main:
	MOVE.L	#crntdir,workbuf   ;'',0 (crntdir) = the current dir when
				   ; this program was run.
lok:				   ;Else use use the one typed as an arg.
	MOVE.L	workbuf,LockD
	MOVE.L	LockD,d1
	MOVE.L	#-2,d2		;-2  = Read
	MOVEA.L	dosbase(pc),a6
	JSR	_LVOLock(a6)	;Get file key.
	TST.L	d0		;Dir not found? If yes then
	BEQ	dnotf		; print warning msg and quit
	MOVE.L	d0,LockD	;Save lock
	TST.B	browseflag	;Only looking, not accepting yet
	BNE.S	exami
	CMPI.B	#QMARK,qmflag
	BEQ	pmsg
	TST.B	cdirflag		;Was '',0 (crntdir) used?
	BEQ	acptd		;If yes then we start showing dir
				; names. Else make arg current dir.
;	TST.B	(a5)+
exami:
	MOVE.L	LockD,d1	;Buffer holding current Lock.
	MOVE.L	FibA,d2		;Buffer for holding FileInfoBlock.
	JSR	_LVOExamine(a6)	;Fills FileInfoBlock.
	TST.L	d0
	BEQ	qu

exnex:
	MOVE.L	LockD,d1	;Buffer holding current lock
	MOVE.L	FibA,d2		;FileInfoBlock
	JSR	_LVOExNext(a6)	;Get the next directory entry.
	TST.L	d0		;d0 = 0 = no more files. If DirNameD = 0
				; there are'nt any dirs.
	BEQ.S	beadir		;Check. Were there any directories?
	;If no, give warning and quit. If yes, keep showing thier names.

						;Offsets within FileInfoBlock
	MOVE.L	fib_DirEntryType(FibA),d1	; for obtaining Entry type.
	;CMP.L	#-3,d1		;-3 = file 2 = directory
	BMI	exnex		;Keep looking for a directory.
	MOVEA.L	FibA,a0		;Work on a0. Don't disturb a4 (FibA)
	;Could use #fib_FileName instead of d7. It's quicker this way.
	ADDA.L	d7,a0		;Get address of filename by adding
				; #fib_FileName value
	MOVE.L	a0,d2		;Ready for writing
	MOVE.L	d2,DirNameD	;Sets a flag (DirNameD)

	MOVEQ	#0,d3
getlgth:
	ADDQ	#1,d3	;Get the length of DIR name
	TST.B	(a0)+	;Keeps looping until 0
	BNE.S	getlgth
	MOVE.L	OutHdlD,d1
	JSR	_LVOWrite(a6)

	;Keep looping until suitable key is pressed.
	;A WaitChar or something would be nicer.
keyper:
	MOVE.B	#RTNup,KEYPRESS	;ReTurN up
chkey:
	CMP.B	#RTNup,KEYPRESS
	BEQ.S	chkey
	MOVE.B	KEYPRESS,keyb
	CMP.B	#LALTdown,keyb	;Left ALTed to abort
	BEQ.S	nullf		;LF then quit
	CMP.B	#RSHIFT,keyb	;Right SHIFT down to make dir current
	BEQ.S	nullf
	CMP.B	#RTNdown,keyb	;RETURN down to make dir current
	BEQ.S	accept
	CMP.B	#RALTdown,keyb	;Right ALT
	BEQ.S	nullf		;Give LF then look for next dir name
	BRA.S	keyper

beadir:	;If DirNameD is empty then there aren't any directories here.
	TST.B	DirNameD	;Were there any dirs? If there were'nt
	BNE.S	exami		; say so and quit. Else keep showing
				; the directory names for selection.
	MOVE.L	#nildirs,d2
	MOVE.L	#ndlgth,d3
leave:
	MOVE.L	OutHdlD,d1
	MOVEA.L	dosbase(pc),a6
	JSR	_LVOWrite(a6)
	TST.L	arpbase		;For when arp is not avialable
	BEQ	arpq
	BRA	qu

nullf:		;Give a linefeed every time right ALT is pressed.
	MOVE.L	OutHdlD,d1
	MOVE.L	#lfnull,d2
	MOVE.L	#1,d3
	JSR	_LVOWrite(a6)
	CMP.B	#LALTdown,keyb	;Left ALTed to quit.
	BEQ.S	chknoarg
	TST.B	browseflag	;Aborted in browse mode therefore
	BNE.S	accept		; the initial lock must be unlocked
	CMP.B	#RSHIFT,keyb	;Right SHIFT down to make dir current
	BEQ.S	accept
	BRA	exnex		;Get another directory name.

chknoarg:
	TST.B	cdirflag
	BNE	qu
accept:
	CMP.B	#RALTdown,keyb	;We end up here because the browseflag
	BEQ	exnex		; was set and Right ALT was pressed
	MOVE.L	LockD,d1
	JSR	_LVOUnLock(a6)
	CMP.B	#LALTdown,keyb	;Left ALTed to quit.
	BEQ	qu		;Had to UnLock the initial Lock
	MOVE.L	DirNameD,d1
	TST.B	browseflag	;If we started in browse mode, we'll
	BNE.S	makepath	; have to generate complete path for Lock()
havepath:		;Returns here after making string representing path
	MOVE.L	#-2,d2		;-2  = Read
	JSR	_LVOLock(a6)	;Get file key.
	TST.L	d0		;Incase of fire
	BEQ	qu
	MOVE.L	d0,LockD	;Final lock
acptd:
	MOVE.L	LockD,d1
	JSR	_LVOCurrentDir(a6)	;Updates cli_CurrentDir but not
					; the prompt... pity.

;*** This next bit uses the ARP function PathName to make the string
;***  to update the prompt with.
;If I didn't use PathName I would've had to call the ParentDir, Lock, Examine
;functions until the root dir was reached plucking out the fib_FileNames as
;I went and storing them somewhere, and then get them into the correct order
;becuase at that stage they would be in reverse order, and then put them into
;cli_SetName. Or something like that.
;PathName is a lot easier to use.
;2.0 has a function called SetCurrentDirName() but whether that or another
;2.0 function helps generate the path string I don't know.
pmsg:
	MOVEA.L	arpbase(pc),a6
	MOVE.L	a6,d0
	MOVEA.L	EXECBASE,a0
	MOVEA.L	$114(a0),a5	:ThisTask
	MOVE.L	$ac(a5),d4	;pr_Cli. pointer to this cli process
	ASL.L	#2,d4		;BPTR
	MOVEA.L	d4,a3		;pr_Cli. pointer to cli process
	SUBA.W  #128,A7		;128 byte buffer
	MOVEA.L A7,A0		;Initialize buffer for PathName()
	MOVE.L  LockD,D0	;Lock for PathName()
	MOVEQ   #8,D1		;Max number of dir names

	; Return Complete pathname of file/directory in buffer. Length in d0
	JSR     -$14A(A6)     ;PathName(lock,buffer,componentcount)(d0/a0,d1)
	move.l	a0,d2		;Path string gets put in buffer (a0)
	MOVE.L  4(A3),D1	;cli_SetName name of current directory
	ASL.L	#2,d1
	MOVEA.L	d1,a1
	MOVE.B	d0,(a1)+	;BPTR wants length first
	MOVEQ	#0,d3
pmtlop:	
	CMPI.L	#78,d3		;79 is the limit for cli_SetName
	BEQ.S	pmtlopout
	ADDQ	#1,d3
	MOVE.B	(a0)+,(a1)+
	BNE	pmtlop
pmtlopout:
	ADDA.W  #128,A7
;*** Thats the prompt updated

	TST.B	qmflag	;Don't want any messages
	BEQ	qu	; when directory has been selected
	BRA.S	justmsg

makepath:			;Generate complete path for Lock()
	MOVEA.L	DirNameD,a0	; in browse mode.
mploop:	MOVE.B	(a0)+,(a5)+	;String going into workbuf
	BNE.S	mploop
	MOVE.L	workbuf,d1	;Address of full path
	BRA.S	havepath

;** Prints dir not found msg
dnotf:	MOVE.L	workbuf,a1	;Dir not found
	MOVE.L	arglen,d7
	MOVEQ	#0,d3
	SUBQ.L	#1,d7	;Stamp on the LF
	ADDA	d7,a1
	MOVE.L	d7,d3
	LEA	warn(pc),a2
warnloop:
	ADDQ	#1,d3
	MOVE.B   (a2)+,(a1)+
	BNE	warnloop
	MOVE.L	workbuf,d2
	BRA	wmsg

;*** This next lot gives the instruction message
;Could decrease program size by leaving this stuff out but such a
;radical program needs some explanation.
justmsg:
	MOVE.L	workbuf,a1
	MOVEQ	#0,d3
	MOVE.L	16(a3),d0	;cli_CommandName
	ASL.L	#2,d0

	MOVEA.L	d0,a0
	TST.B	(a0)+	;Cut off BCPL string size
nameloop: ADDQ	#1,d3
	MOVE.B	(a0)+,(a1)+
	BNE	nameloop

	LEA	eg1(pc),a2
egloop1: ADDQ	#1,d3
	MOVE.B   (a2)+,(a1)+
	BNE	egloop1

	MOVEA.L	d2,a0	;Path string address
pmptloop1: ADDQ	#1,d3
	MOVE.B	(a0)+,(a1)+
	BNE	pmptloop1

	LEA	eg1a(pc),a2
egloop1a:	ADDQ	#1,d3
	MOVE.B   (a2)+,(a1)+
	BNE	egloop1a

	MOVEA.L	d0,a0		;Could've had just one of these
	TST.B	(a0)+		; and jsr'ed to it.
nameloop1: ADDQ	#1,d3		;This is quicker and
	MOVE.B	(a0)+,(a1)+	; the program remains the same
	BNE	nameloop1	; size either way

	LEA	eg2(pc),a2
egloop2:	ADDQ	#1,d3
	MOVE.B   (a2)+,(a1)+
	BNE	egloop2

	MOVEA.L	d2,a0
pmptloop2: ADDQ	#1,d3
	MOVE.B	(a0)+,(a1)+
	BNE	pmptloop2

	LEA	eg1a(pc),a2
egloop2a:	ADDQ	#1,d3
	MOVE.B   (a2)+,(a1)+
	BNE	egloop2a

	MOVEA.L	d0,a0
	TST.B	(a0)+	;Cut off BCPL string size
nameloop2: ADDQ	#1,d3
	MOVE.B	(a0)+,(a1)+
	BNE	nameloop2

	LEA	eg3(pc),a2
egloop3:	ADDQ	#1,d3
	MOVE.B   (a2)+,(a1)+
	BNE	egloop3

	MOVEA.L	d2,a0
pmptloop3: ADDQ	#1,d3
	MOVE.B	(a0)+,(a1)+
	BNE	pmptloop3

	LEA	eg1a(pc),a2
egloop3a:	ADDQ	#1,d3
	MOVE.B   (a2)+,(a1)+
	BNE	egloop3a

	MOVEA.L	d0,a0
	TST.B	(a0)+
nameloop3: ADDQ	#1,d3
	MOVE.B	(a0)+,(a1)+
	BNE	nameloop3

	LEA	eg4(pc),a2
egloop4:	ADDQ	#1,d3
	MOVE.B   (a2)+,(a1)+
	BNE	egloop4

wmsg:	MOVE.L	workbuf,d2
	MOVE.L	OutHdlD,d1
	MOVEA.L	dosbase(pc),a6
	JSR	_LVOWrite(a6)
;*** Message recieved *********
	
qu:	MOVEA.L	arpbase(pc),a1	;DOS.lib close
	MOVEA.L	EXECBASE,a6
	JSR	_LVOCloseLibrary(a6)

arpq:	MOVEA.L	EXECBASE,a6
	MOVEA.L	FibA,a1
	MOVE.L	#fib_SIZEOF+WORKBUF,d0
	JSR	_LVOFreeMem(a6)

allocq:	MOVEA.L	dosbase(pc),a1	;DOS.lib close
	MOVEA.L	EXECBASE,a6
	JSR	_LVOCloseLibrary(a6)

quickqu:	RTS
           END

/*** C example: ***/
void
Update_Prompt(char *currentdir)
/* Update the concerned field with the new current dir									*/
/* This routine is for users of wshell or such, that display this name	*/
/* as the shell prompt.																	*/
  struct Process *pr = (struct Process *) FindTask(0L);
  struct CommandLineInterface *cli =
  (struct CommandLineInterface *) (pr -> pr_CLI) << 2;
  char *p = (char *) (cli -> cli_SetName) << 2;
  
  *p++ = strlen(currentdir);		/* it is aBSTR, so length first */
  while (*currentdir) *p++ = *currentdir++;   /* don't add '\0' */
}

;Assembly example to get string into where prompt can find it:
; (my own interpretation)
proc		equr	a5
cli		equr	d4
StartUp:
	move.l	4,a6		;get exec library base pointer
	movea.l	$114(a6),proc	;ThisTask
	move.l	$ac(proc),cli	;pr_CLI
	asl.l	#2,cli		;BPTR
	movea.l	cli,a3
	move.l	$4(a3),cli	;cli_SetName. Address where path string
	asl.l	#2,cli		; needs to be
	move.l	cli,a0
	move.l	#cdir,a1
	moveq	#0,d0
cdlop:
	cmpi.l	#79,d0		;Limited to 79 chars
	beq	qu
	addq	#1,d0
	move.b	(a1)+,(a0)+	;One char at time into cli_SetName where
	bne.s	cdlop		; prompt can find it. The prompt will now
				; have RAM:t in it, if you're using a shell
qu:	rts

cdir	dc.b	'5RAM:t',0	;BSTR wants size first
	end
