		*********************************
		*                               *
		*   Visual Shell v1.17  10/92   *
		*                               *
		*     by Torsten Jürgeleit      *
		*                               *
		*   assembly support routines   *
		*            part I             *
		*                               *
		*********************************

	NOLIST
	INCLUDE "exec/types.i"
	INCLUDE "exec/ports.i"
	INCLUDE "exec/nodes.i"
	INCLUDE "exec/lists.i"
	INCLUDE "exec/memory.i"
	INCLUDE "devices/conunit.i"
	INCLUDE "devices/inputevent.i"
	INCLUDE "libraries/dos.i"
	INCLUDE "libraries/dosextens.i"
	INCLUDE "libraries/arpbase.i"
	INCLUDE "intuition/intuition.i"
	INCLUDE "equates.i"
	LIST

;---------------------------------------------------------------------------
; External definitions
;---------------------------------------------------------------------------

	XDEF	_input_event_handler
	XDEF	_get_list_node
	XDEF	_build_file_node
	XDEF	_build_dev_node
	XDEF	_insert_file_node
	XDEF	_search_file_node
	XDEF	_count_file_nodes
	XDEF	_recount_file_nodes
	XDEF	_sum_file_sizes
	XDEF	_get_view_mode
	XDEF	_build_view_line_list
	XDEF	_get_first_visible_line
	XDEF	_search_line_node
	XDEF	_protection
	XDEF	_build_limited_path_name

;---------------------------------------------------------------------------
; External references
;---------------------------------------------------------------------------

	XREF	_IntuitionBase
	XREF	_GfxBase
	XREF	_gadget_buffer
	XREF	_con_device
	XREF	_con_unit
	XREF	_con_window
	XREF	_con_rport
	XREF	_main_task
	XREF	_vsh_port
	XREF	_event_mode
	XREF	_auto_repeat
	XREF	_show_flag
	XREF	_scroll_flag
	XREF	_vsh_scroll_speed
	XREF	_vsh_keyboard_type
	XREF	_qualifier
	XREF	_enable_abort
	XREF	_num_lock
	XREF	_key_table
	XREF	_action_table
	XREF	_scroll_flag_table
	XREF	_action
	XREF	_file_req
	XREF	_view_req
	XREF	_active_freq
	XREF	_line1_buffer
	XREF	_max_line_len
	XREF	_print_dir_info
	XREF	_print_info_line
	XREF	_print_vreq_status
	XREF	_print_quick_view
	XREF	_SPrintf
	XREF	_protection_bits
	XREF	_protection_string
	XREF	_font_data
	XREF	_template_buffer
	XREF	write_text

;---------------------------------------------------------------------------
; struct InputEvent  *input_event_handler(event, data)
;					   a0	  a1
;	struct InputEvent  *event
;	APTR data = data segment ptr for a4
; Tied into input chain and parses event list for VSh special keys
;---------------------------------------------------------------------------
_input_event_handler:
	PUSH	a0-a6/d0-d7

	move.l	a1,a4				; a4 := data segment ptr
	move.l	_IntuitionBase,a1
	move.l	ib_ActiveWindow(a1),a1
	cmp.l	_con_window,a1			; input for VSh con window ?
	bne	ieh_exit
	move.l	a0,a2				; a2 := event
	move.l	_main_task,a3			; a3 := task ptr
	move.l	(4).w,a6			; a6 := exec base ptr
	move.l	_vsh_port,a0			; a0 := msg port
	moveq	#0,d0
	move.b	MP_SIGBIT(a0),d0		; d0 := signal bit
	moveq	#0,d4
	bset	d0,d4				; d4 := signal mask
	moveq	#0,d5				; clear "don't clear event" flag
	bra	ieh_check_next_event

ieh_event_loop:
	move.b	ie_Class(a2),d1			; check ie_Class

ieh_check_raw_mouse:
	cmp.b	#IECLASS_RAWMOUSE,d1		; raw mouse event ?
	bne	ieh_check_raw_key
	move.w	ie_Code(a2),d0			; check ie_Code
	cmp.w	#IECODE_LBUTTON,d0		; left or right button pressed ?
	beq	ieh_mouse_button_pressed
	cmp.w	#IECODE_RBUTTON,d0
	bne	ieh_next_event

ieh_mouse_button_pressed:
	move.b	_event_mode,d0			; input for input or config gadget ?
	cmp.b	#EVENT_MODE_INPUT,d0		; if yes, don't report any mouse button events
	beq	ieh_clear_event
	cmp.b	#EVENT_MODE_CONFIG,d0
	beq	ieh_clear_event
	bra	ieh_next_event

ieh_check_raw_key:
	cmp.b	#IECLASS_RAWKEY,d1		; raw key event ?
	bne	ieh_next_event
	cmp.b	#EVENT_MODE_IGNORE,_event_mode	; ignore input for VSh ?
	beq	ieh_clear_event
	move.w	ie_Code(a2),d2			; d2 := key code
	move.w	ie_Qualifier(a2),d3		; d3 := qualifier
	and.w	#QUALIFIER_MASK,d3		; mask unused qualifier bits

ieh_search_key:
	lea	_key_table,a0			; search raw key code in table
	move.w	d2,d1				; d1 := key code
	bclr	#IECODEB_UP_PREFIX,d1		; mask key up prefix
	moveq	#MAX_ACTION_KEYS-1,d0

ieh_search_key_loop:
	cmp.w	(a0)+,d1			; key code in table ?
	beq	ieh_get_qualifier_bit_or_action_code
	dbra	d0,ieh_search_key_loop

ieh_unknown_key:
	btst	#IECODEB_UP_PREFIX,d2		; ignore key up events
	bne	ieh_next_event
	move.b	_event_mode,d0			; d0 := event mode

ieh_unknown_key_for_config:
	cmp.b	#EVENT_MODE_CONFIG,d0		; input for config gadget ?
	bne	ieh_unknown_key_for_input
	btst	#IEQUALIFIERB_REPEAT,d3		; ignore auto repeat
	bne	ieh_next_event
	cmp.w	#IECODE_UP_ARROW,d2
	beq	ieh_config_up_arrow
	cmp.w	#IECODE_DOWN_ARROW,d2
	bne	ieh_next_event

ieh_config_down_arrow:
	move.w	#IECODE_RETURN,ie_Code(a2)	; convert arrow key code to return
	moveq	#VSH_ACTION_GADGET_END_KEY,d1	; d1 := action code
	bra	ieh_set_config_or_input_key_action

ieh_config_up_arrow:
	move.w	#IECODE_RETURN,ie_Code(a2)	; convert arrow key code to return
	moveq	#VSH_ACTION_SHIFT_GADGET_END_KEY,d1	; d1 := action code
	bra	ieh_set_config_or_input_key_action

ieh_unknown_key_for_input:
	cmp.b	#EVENT_MODE_INPUT,d0		; input for input gadget ?
	bne	ieh_unknown_key_for_speed_search
	moveq	#VSH_ACTION_SPEED_SEARCH,d1	; d1 := action code

ieh_set_config_or_input_key_action:
	moveq	#1,d5				; set "don't clear event" flag
	bra	ieh_set_action

ieh_unknown_key_for_speed_search:
	tst.b	_num_lock			; num lock set ?
	beq	ieh_next_event			; if no, normal alt keys
	btst	#IEQUALIFIERB_REPEAT,d3		; ignore auto repeat
	bne	ieh_next_event
	cmp.w	#IEQUALIFIER_LALT,d3		; alt key + letter -> speed search
	beq	ieh_set_speed_search_action
	cmp.w	#IEQUALIFIER_RALT,d3
	bne	ieh_next_event

ieh_set_speed_search_action:
	bsr	raw_key_convert			; convert raw key event to ASCII
	cmp.l	#1,d0				; one char converted ?
	bne	ieh_next_event
	moveq	#VSH_ACTION_SPEED_SEARCH,d1	; d1 := action code
	bra	ieh_set_action

ieh_get_qualifier_bit_or_action_code:
	lea	_action_table,a0
	moveq	#MAX_ACTION_KEYS-1,d1
	sub.w	d0,d1
	move.w	d1,d0				; d0 := pos in key code table
	move.b	(a0,d0.w),d1			; d1 := qualifier bit or action code
	cmp.w	#MAX_QUALIFIER_KEYS,d0		; check pos in action table
	bhs	ieh_action_code

ieh_qualifier_bit:
	btst	#IECODEB_UP_PREFIX,d2		; qualifier key up ?
	bne	ieh_qualifier_up

ieh_qualifier_down:
	bset	d1,_qualifier			; set qualifier bit
	bra	ieh_set_qualifier_action

ieh_qualifier_up:
	bclr	d1,_qualifier			; clear qualifier bit

ieh_set_qualifier_action:
	cmp.b	#EVENT_MODE_NORMAL,_event_mode	; normal event mode ?
	bne	ieh_next_event
	moveq	#VSH_ACTION_QUALIFIER,d1	; d1 := action code
	bra	ieh_set_action			; set action only for none gadget input event

ieh_action_code:
	bclr	#IECODEB_UP_PREFIX,d2		; ignore key up events
	bne	ieh_clear_flags
	move.b	_event_mode,d0			; d0 := event mode
	cmp.b	#EVENT_MODE_INPUT,d0		; input for input or config gadget ?
	beq	ieh_check_end_gadget
	cmp.b	#EVENT_MODE_CONFIG,d0
	bne	ieh_check_return

ieh_check_config_fkeys:
	cmp.b	#VSH_ACTION_F10,d1		; f1 - f4 or f10 pressed ?
	beq	ieh_convert_config_fkey
	cmp.w	#VSH_ACTION_F4,d1
	bhi	ieh_check_end_gadget		; illegal fkey

ieh_convert_config_fkey:
	tst.w	d3				; no qualifier ?
	bne	ieh_next_event
	move.w	#IECODE_RETURN,ie_Code(a2)	; convert fkey code to return
	bra	ieh_set_end_gadget_action

ieh_check_end_gadget:
	cmp.b	#VSH_ACTION_ESC,d1		; esc key pressed ?
	beq	ieh_convert_esc_to_return
	cmp.b	#VSH_ACTION_RETURN,d1		; return pressed ?
	beq	ieh_check_shift_end_gadget
	cmp.b	#VSH_ACTION_NUM_ENTER,d1	; enter pressed ?
	bne	ieh_next_event

ieh_check_shift_end_gadget:			; shifted finish gadget input ?
	bclr	#IEQUALIFIERB_NUMERICPAD,d3
	cmp.w	#IEQUALIFIER_LSHIFT,d3		; shift key pressed ?
	beq	ieh_shift_end_gadget
	cmp.w	#IEQUALIFIER_RSHIFT,d3
	beq	ieh_shift_end_gadget

ieh_end_gadget:
	move.b	#VSH_ACTION_GADGET_END_KEY,d1	; d1 := action code
	bra	ieh_set_end_gadget_action

ieh_shift_end_gadget:				; shift return or enter for jump to next entry
	move.b	#VSH_ACTION_SHIFT_GADGET_END_KEY,d1	; d1 := action code
	cmp.b	#EVENT_MODE_CONFIG,d0		; input for config ?
	bne	ieh_set_action
	and.w	#~(IEQUALIFIER_LSHIFT|IEQUALIFIER_RSHIFT),ie_Qualifier(a2)
	bra	ieh_set_end_gadget_action

ieh_convert_esc_to_return
	move.w	#IECODE_RETURN,ie_Code(a2)	; convert esc key code to return

ieh_set_end_gadget_action:
	bclr	#IEQUALIFIERB_REPEAT,d3		; ignore auto repeat
	bne	ieh_clear_event
	moveq	#1,d5				; set "don't clear event" flag
	cmp.b	#EVENT_MODE_INPUT,_event_mode
	bne	ieh_set_action
	move.b	#EVENT_MODE_NORMAL,_event_mode	; reset input event mode to normal
	bra	ieh_set_action

ieh_check_return:
	cmp.b	#VSH_ACTION_RETURN,d1		; return key pressed ?
	beq	ieh_next_event

ieh_check_repeat:
	bclr	#IEQUALIFIERB_REPEAT,d3		; auto repeat ?
	beq	ieh_check_numlock_key
	cmp.b	#VSH_ACTION_SCROLL_PAGE_UP,d1	; check action for autorepeatable action
	blo	ieh_clear_flags
	cmp.b	#VSH_ACTION_MARK_FILE,d1
	bhi	ieh_clear_flags
	move.b	#1,_auto_repeat			; set auto repeat flag
	tst.b	_num_lock			; num lock set ?
	beq	ieh_next_event			; if no, normal numeric pad keys
	bra	ieh_check_shift

ieh_check_numlock_key:
	cmp.b	#KEYBOARD_TYPE1,_vsh_keyboard_type
	bne	ieh_numlock_keyboard_type2

ieh_numlock_keyboard_type1:
	cmp.b	#VSH_ACTION_NUM_MINUS,d1	; numlock key pressed ?
	bne	ieh_check_numlock
	cmp.w	#IEQUALIFIER_LALT|IEQUALIFIER_NUMERICPAD,d3
	beq	ieh_set_numlock_action
	cmp.w	#IEQUALIFIER_RALT|IEQUALIFIER_NUMERICPAD,d3
	bne	ieh_check_numlock
	bra	ieh_set_numlock_action

ieh_numlock_keyboard_type2:
	cmp.b	#VSH_ACTION_NUM_LEFT_BRAKET,d1	; numlock key pressed ?
	bne	ieh_check_numlock

ieh_set_numlock_action:
	moveq	#VSH_ACTION_NUMLOCK,d1		; d1 := action code
	bra	ieh_set_action

ieh_check_numlock:
	tst.b	_num_lock			; num lock set ?
	beq	ieh_next_event			; if no, normal numeric pad keys

ieh_check_control:
	btst	#IEQUALIFIERB_CONTROL,d3	; control key pressed ?
	beq	ieh_check_shift
	and.w	#~(IEQUALIFIER_CONTROL),ie_Qualifier(a2)	; clear control qualifier bit
	bra	ieh_next_event			; normal events

ieh_check_shift:
	bclr	#IEQUALIFIERB_NUMERICPAD,d3
	cmp.w	#IEQUALIFIER_LSHIFT,d3		; shift key pressed ?
	beq	ieh_shift_action
	cmp.w	#IEQUALIFIER_RSHIFT,d3
	bne	ieh_check_alternate

ieh_shift_action:
	add.b	#SHIFTED,d1			; change to shifted action code
	bra	ieh_set_action

ieh_check_alternate:
	cmp.w	#IEQUALIFIER_LALT,d3		; alternate key pressed ?
	beq	ieh_alternate_action
	cmp.w	#IEQUALIFIER_RALT,d3
	bne	ieh_check_alternate_shift

ieh_alternate_action:
	add.b	#ALTERNATED,d1			; change to alternated action code
	bra	ieh_set_action

ieh_check_alternate_shift:
	cmp.w	#IEQUALIFIER_LSHIFT|IEQUALIFIER_LALT,d3		; alternate + shift key pressed ?
	beq	ieh_alternate_shift_action
	cmp.w	#IEQUALIFIER_RSHIFT|IEQUALIFIER_RALT,d3
	bne	ieh_check_qualifier

ieh_alternate_shift_action:
	add.b	#ALTERNATED+SHIFTED,d1		; change to alternated + shifted action code
	bra	ieh_set_action

ieh_check_qualifier:
	tst.w	d3				; other qualifier ?
	bne	ieh_next_event

ieh_set_action:
	move.b	d1,_action

ieh_check_scrolling:
	cmp.b	#VSH_ACTION_SCROLL_TOP,d1	; check action for scrolling action
	blo	ieh_check_esc
	cmp.b	#VSH_ACTION_SCROLL_RIGHT,d1
	bhi	ieh_check_esc
	lea	_scroll_flag_table,a0		; set scroll flag
	sub.b	#VSH_ACTION_SCROLL_TOP+1,d1
	ext.w	d1
	move.b	(a0,d1.w),_scroll_flag
	bra	ieh_signal_task

ieh_check_esc:
	tst.b	_enable_abort			; ESC abort enabled ?
	beq	ieh_signal_task
	cmp.b	#VSH_ACTION_ESC,d1		; ESC key pressed ?
	bne	ieh_signal_task

ieh_set_ctrl_c:
	move.l	#SIGBREAKF_CTRL_C,d0		; d0 := signal mask
	move.l	a3,a1				; a1 := task ptr
	CALLSYS	Signal				; set ctrl c signal

ieh_signal_task:
	move.l	d4,d0				; d0 := signal mask
	move.l	a3,a1				; a1 := task ptr
	CALLSYS	Signal				; signal main task

ieh_check_dont_clear_event_flag:
	tst.b	d5				; "don't clear event" flag set ?
	bne	ieh_next_event			; if yes, don't clear this event
	bra	ieh_clear_event

ieh_clear_flags:
	clr.b	_auto_repeat
	clr.b	_scroll_flag

ieh_clear_event:
	move.w	#IECLASS_NULL,ie_Class(a2)

ieh_next_event:
	move.l	ie_NextEvent(a2),a2		; get next event in list

ieh_check_next_event:
	move.l	a2,d0				; next event ptr == NULL ?
	bne	ieh_event_loop

ieh_exit:
	PULL	a0-a6/d0-d7
	move.l	a0,d0				; return old event list ptr
	rts

;---------------------------------------------------------------------------
; BOOL raw_key_convert(event)
;			a2
;	struct InputEvent  *event
; Convert a raw key input event into an ASCII char
;---------------------------------------------------------------------------
raw_key_convert:
	PUSH	a2/a6

	clr.w	ie_Qualifier(a2)		; clear qualifier -> no alt
	move.l	a2,a0				; a0 := input event ptr
	lea	_gadget_buffer,a1		; a1 := buffer ptr
	moveq	#1,d1				; d1 := buffer length
	move.l	_con_unit,a2			; get ConUnit ptr
	lea	cu_KeyMapStruct(a2),a2		; a2 := keymap ptr
	move.l	_con_device,a6			; get ConsoleBase ptr
	CALLSYS	RawKeyConvert
	lea	_gadget_buffer,a1		; a1 := buffer ptr
	clr.b	1(a1)				; set end of string

	PULL	a2/a6
	rts

;---------------------------------------------------------------------------
; struct MinNode  *get_list_node(list, num_entries, num)
;				  a0       d0       d1
; 	struct MinList  *list
;	ULONG num_entries, num
; Get specified entry from list
;---------------------------------------------------------------------------
_get_list_node:
	tst.l	d0				; no entries in list ?
	beq	gln_return_null
	sub.l	d1,d0				; num > num_entries ?
	bmi	gln_return_last_node
	cmp.l	d1,d0				; num >= (num_entries - num) ?
	bhs	gln_from_end

gln_from_start:
	move.l	MLH_HEAD(a0),a0			; a0 := first node
	bra	gln_start_check

gln_start_loop:
	move.l	MLN_SUCC(a0),a0			; get next node

gln_start_check:
	subq.l	#1,d1				; dec num
	bne	gln_start_loop
	bra	gln_return_current_node

gln_from_end:
	move.l	MLH_TAILPRED(a0),a0		; a0 := last node
	bra	gln_end_check

gln_end_loop:
	move.l	MLN_PRED(a0),a0			; get previous node

gln_end_check:
	subq.l	#1,d0				; dec num
	bpl	gln_end_loop

gln_return_current_node:
	move.l	a0,d0				; d0 := current node
	rts

gln_return_last_node;
	move.l	MLH_TAILPRED(a0),d0		; d0 := last node
	rts

gln_return_null:
	moveq	#0,d0				; d0 := NULL
	rts

;---------------------------------------------------------------------------
; struct FileNode  *build_file_node(fib)
;				    a0
; 	struct FileInfoBlock  *fib
; Allocate memory for file node and fill it with data from fle info block
;---------------------------------------------------------------------------
_build_file_node:
	PUSH	a2-a3/a6

	move.l	a0,a3				; a3 := fib

	; --- alloc memory for new file node
	moveq	#FileNode_Sizeof,d0
	move.l	#MEMF_PUBLIC|MEMF_CLEAR,d1
	move.l	(4).w,a6			; a6 := exec base
	CALLSYS	AllocMem
	tst.l	d0
	beq	bfn_error
	move.l	d0,a2				; a2 := file node ptr

	; --- init file node
	lea	fib_FileName(a3),a0		; source ptr
	lea	fn_Text(a2),a1			; dest ptr
	moveq	#0,d0				; reset name len counter

bfn_copy_loop:
	move.b	(a0)+,d1			; copy a char till null
	beq	bfn_fill_spaces
	move.b	d1,(a1)+
	addq.w	#1,d0
	cmp.w	#MAX_FILE_NAME_LEN,d0
	blo	bfn_copy_loop

bfn_fill_spaces:
	move.w	d0,fn_NameLen(a2)		; save name len
	moveq	#' ',d1
	bra	bfn_check_len

bfn_fill_loop:
	move.b	d1,(a1)+			; fill string with spaces
	addq.w	#1,d0

bfn_check_len:
	cmp.w	#MAX_FILE_NAME_LEN+1,d0		; one additional space as delimiter
	blo	bfn_fill_loop

bfn_copy_date:
	move.l	fib_DateStamp+ds_Days(a3),fn_Date+ds_Days(a2)
	move.l	fib_DateStamp+ds_Minute(a3),fn_Date+ds_Minute(a2)
	move.l	fib_DateStamp+ds_Tick(a3),fn_Date+ds_Tick(a2)

bfn_copy_protection:
	move.l	fib_Protection(a3),d0
	tst.b	fib_Comment(a3)			; any comment ?
	beq	bfn_no_comment
	bset	#MAX_PROTECTION_BITS-1,d0	; set comment bit

bfn_no_comment:
	move.w	d0,fn_Protection(a2)

bfn_check_type:
	moveq	#ENTRY_TYPE_FILE,d0		; default type = file
	lea	bfn_size_fmt_string,a0		; default size text = number
	tst.l	fib_DirEntryType(a3)		; check file type
	bmi	bfn_set_type			; < 0 => it's a file
	moveq	#ENTRY_TYPE_DIR,d0		; type = dir
	lea	bfn_dir_fmt_string,a0		; size text = dir

bfn_set_type:
	move.b	d0,fn_Type(a2)			; set file type
	move.l	fib_Size(a3),d0
	move.l	d0,fn_Size(a2)			; set file size
	move.l	d0,-(sp)			; size -> only valid for files
	move.l	a0,-(sp)			; format string
	move.l	a1,-(sp)			; dest string
	CALL	_SPrintf			; append size to string
	lea	3*4(sp),sp
	move.l	a2,d0				; return value

bfn_exit:
	PULL	a2-a3/a6
	rts

bfn_error:
	moveq	#0,d0
	bra	bfn_exit

bfn_size_fmt_string:
	dc.b	"%8ld",0
bfn_dir_fmt_string:
	dc.b	"   (Dir)",0

;---------------------------------------------------------------------------
; struct FileNode  *build_dev_node(dlist)
;				    a0
; 	struct DosList  *dlist
; Allocate memory for a device node and fill it with data from dos list
;---------------------------------------------------------------------------
_build_dev_node:
	PUSH	a2-a3/a6

	move.l	a0,a3				; a3 := dos list

	; --- alloc memory for file node
	moveq	#FileNode_Sizeof,d0
	move.l	#MEMF_PUBLIC|MEMF_CLEAR,d1
	move.l	(4).w,a6			; a6 := exec base
	CALLSYS	AllocMem
	tst.l	d0
	beq	bdn_error
	move.l	d0,a2				; a2 := file node ptr

	; --- init file node
	move.l	a3,d0				; dlist == NULL ?
	bne	bdn_no_ram_disk

bdn_ram_disk:
	move.w	#4,fn_NameLen(a2)		; build a device node for RAM:
	lea	bdn_ram_string,a0		; source ptr
	lea	fn_Text(a2),a1			; dest ptr
	moveq	#1,d0				; type := device
	bra	bdn_set_type

bdn_no_ram_disk:
	move.l	dvi_Name(a3),d0
	lsl.l	#2,d0				; make BPTR to PTR
	move.l	d0,a0
	move.b	(a0),d1				; d1 := length of BSTR
	add.l	#1,a0				; source ptr
	lea	fn_Text(a2),a1			; dest ptr
	moveq	#0,d0				; rest name len counter

bdn_copy_loop:
	move.b	(a0)+,(a1)+			; copy a char
	addq.w	#1,d0
	cmp.w	#MAX_FILE_NAME_LEN,d0
	bhs	bdn_append_colon
	cmp.b	d1,d0				; end of name ?
	blo	bdn_copy_loop

bdn_append_colon:
	move.b	#':',(a1)+			; append colon to name
	add.w	#1,d0

bdn_fill_line:
	move.w	d0,fn_NameLen(a2)		; save name len
	moveq	#' ',d1
	bra	bdn_check_len

bdn_fill_loop:
	move.b	d1,(a1)+			; fill string with spaces
	addq.w	#1,d0

bdn_check_len:
	cmp.w	#MAX_FILE_NAME_LEN+1,d0		; one additional space as delimiter
	blo	bdn_fill_loop

	moveq	#ENTRY_TYPE_ASSIGN,d0		; default type = assign
	lea	bdn_assign_string,a0		; default text = assign
	tst.l	dvi_Type(a3)			; check dos list type
	bne	bdn_set_type			; != 0 => it's a assign
	moveq	#ENTRY_TYPE_DEVICE,d0		; type = device
	lea	bdn_device_string,a0		; text = device

bdn_set_type:
	move.b	d0,fn_Type(a2)			; set file type

bdn_append_type:
	move.b	(a0)+,(a1)+			; append type to text
	bne	bdn_append_type

	move.l	a2,d0				; return value

bdn_exit:
	PULL	a2-a3/a6
	rts

bdn_error:
	moveq	#0,d0
	bra	bdn_exit

bdn_assign_string:
	dc.b	"   (Asn)",0
bdn_device_string:
	dc.b	"   (Dev)",0
bdn_ram_string:
	dc.b	"RAM:                              (Dev)",0

;---------------------------------------------------------------------------
; VOID insert_file_node(node, list)
;			 a0    a1
;	struct FileNode  *node
;	struct MinList   *list
; Insert file node into list according to type and name
;---------------------------------------------------------------------------
_insert_file_node:
	PUSH	a2-a3/a5-a6

	move.l	a0,a2				; a2 := node
	move.l	a1,a3				; a3 := list
	move.l	a3,a5				; save list ptr
	move.l	MLH_HEAD(a3),a3			; a3 := list->mlh_Head

ifn_search_loop:
	tst.l	MLN_SUCC(a3)			; end of list ?
	beq	ifn_insert

	move.l	a2,a0				; a0 := new node
	move.l	a3,a1				; a1 := node from list
	bsr	compare_file_nodes
	tst.b	d0
	beq	ifn_insert
	move.l	MLN_SUCC(a3),a3			; next node
	bra	ifn_search_loop

ifn_insert:
	move.l	a5,a0				; a0 := list
	move.l	a2,a1				; a1 := new node
	move.l	MLN_PRED(a3),a2			; a2 := node from list
	move.l	(4).w,a6			; a6 := exec base
	CALLSYS	Insert

	PULL	a2-a3/a5-a6
	rts

compare_file_nodes:		; (a0 := file node 1, a1 := file node 2)
	move.b	fn_Type(a0),d1			; compare type of entries
	cmp.b	fn_Type(a1),d1
	beq	cfn_alpha			; same type -> sort alphabetical
	bcs	cfn_match			; a0->fn_Type > a1->fn_Type
	bra	cfn_no_match			; a0->fn_Type < a1->fn_Type

cfn_alpha:
	lea	fn_Text(a0),a0			; compare name of entries
	lea	fn_Text(a1),a1

cfn_alpha_loop:
	move.b	(a0)+,d0			; convert char to lower case
	cmp.b	#'A',d0
	blo	1$
	cmp.b	#'Z',d0
	bhi	1$
	add.b	#32,d0

1$:
	move.b	(a1)+,d1			; convert char to lower case
	cmp.b	#'A',d1
	blo	2$
	cmp.b	#'Z',d1
	bhi	2$
	add.b	#32,d1

2$:
	tst.b	d0				; end of first name ?
	beq	3$
	cmp.b	d1,d0
	beq	cfn_alpha_loop

3$:
	sub.b	d1,d0
	bls	cfn_no_match

cfn_match:
	moveq	#1,d0				; file node 1 > file node 2
	rts

cfn_no_match:
	moveq	#0,d0				; file node 1 <= file node 2
	rts

;---------------------------------------------------------------------------
; struct FileNode  *search_file_node(node, name, name_len)
;				      a0    a1      d0
;	struct FileNode  *node
;	UBYTE  *name
;	USHORT name_len
; Search file node in list which file name starts with name
;---------------------------------------------------------------------------
_search_file_node:
	PUSH	d2/a2-a3

	move.l	a1,d1				; node == NULL ?
	beq	sfn_not_found
	move.l	a0,a2				; a2 := node
	move.l	a1,a3				; a3 := name
	move.w	d0,d2				; d2 := name length

sfn_search_loop:
	tst.l	MLN_SUCC(a2)			; end of list ?
	beq	sfn_not_found
	tst.w	d2				; length given ?
	beq	sfn_compare_names
	cmp.w	fn_NameLen(a2),d2		; same name length ?
	bne	sfn_next_entry

sfn_compare_names:
	move.l	a3,a0				; a0 := search name
	lea	fn_Text(a2),a1			; a1 := name from node
	bsr	compare_names
	tst.b	d0
	beq	sfn_found

sfn_next_entry:
	move.l	MLN_SUCC(a2),a2			; next node
	bra	sfn_search_loop

sfn_found:
	move.l	a2,d0				; d0 := file node -> return value

sfn_exit:
	PULL	d2/a2-a3
	rts

sfn_not_found:
	moveq	#0,d0				; d0 := NULL -> return value
	bra	sfn_exit


compare_names:		; (a0 := search name, a1 := name from node)
	move.b	(a0)+,d0			; convert char to lower case
	tst.w	d2				; name length given ?
	bne	1$
	cmp.b	#'A',d0
	blo	1$
	cmp.b	#'Z',d0
	bhi	1$
	add.b	#32,d0

1$:
	move.b	(a1)+,d1			; convert char to lower case
	tst.w	d2				; name length given ?
	bne	2$
	cmp.b	#'A',d1
	blo	2$
	cmp.b	#'Z',d1
	bhi	2$
	add.b	#32,d1

2$:
	tst.b	d0				; end of first name ?
	beq	cn_match
	cmp.b	d1,d0
	beq	compare_names
	bne	cn_no_match

cn_match:
	moveq	#0,d0				; search name == name from node
	rts

cn_no_match:
	moveq	#1,d0				; search name != name from node
	rts

;---------------------------------------------------------------------------
; ULONG count_file_nodes(list)
;			  a0
;	struct MinList  *list
; Count file nodes in list and set position of all nodes
;---------------------------------------------------------------------------
_count_file_nodes:
	move.l	MLH_HEAD(a0),a0			; a0 := first node in list
	moveq	#0,d0				; reset node counter

cfn_loop:
	tst.l	MLN_SUCC(a0)			; end of list ?
	beq	cfn_exit
	addq.l	#1,d0
	move.l	d0,fn_Pos(a0)			; set pos of node
	move.l	MLN_SUCC(a0),a0			; next node
	bra	cfn_loop

cfn_exit:
	rts

;---------------------------------------------------------------------------
; ULONG recount_file_nodes(fnode)
;			    a0
;	struct FileNode  *fnode
; Count file nodes in list from given node and dec position of all nodes
;---------------------------------------------------------------------------
_recount_file_nodes:
	move.l	fn_Pos(a0),d0			; d0 := old fnode counter
	subq.l	#1,d0				; dec node counter, because this fnode will be removed
	bra	rfn_next			; start from next fnode

rfn_loop:
	tst.l	MLN_SUCC(a0)			; end of list ?
	beq	rfn_exit
	addq.l	#1,d0
	move.l	d0,fn_Pos(a0)			; set pos of node

rfn_next:
	move.l	MLN_SUCC(a0),a0			; next node
	bra	rfn_loop

rfn_exit:
	rts

;---------------------------------------------------------------------------
; VOID sum_file_sizes(freq)
;		       a0
;	struct FileRequest  *freq
; Count dirs and files in list and calc total size of all files
;---------------------------------------------------------------------------
_sum_file_sizes:
	PUSH	d2-d3

	move.l	a0,a1				; a1 := freq ptr
	moveq	#0,d1				; reset dir counter
	moveq	#0,d2				; reset file counter
	moveq	#0,d3				; reset size counter
	move.l	fr_Display+d_List(a1),a0	; a0 := file list ptr
	move.l	MLH_HEAD(a0),a0			; a0 := first node in list
	move.l	fr_Display+d_NumEntries(a1),d0	; d0 := entry count
	beq	sfs_exit

sfs_loop:
	cmp.b	#ENTRY_TYPE_FILE,fn_Type(a0)	; file ?
	beq	sfs_file

sfs_dir:
	addq.l	#1,d1				; inc dir counter
	bra	sfs_next

sfs_file:
	addq.l	#1,d2				; inc file counter
	add.l	fn_Size(a0),d3			; add size

sfs_next:
	move.l	MLN_SUCC(a0),a0			; next node
	subq.l	#1,d0
	bne	sfs_loop

sfs_exit:
	lea	fr_Info(a1),a0			; save data
	move.l	d1,i_Dirs(a0)
	move.l	d2,i_Files(a0)
	move.l	d3,i_FileSizes(a0)

	PULL	d2-d3
	rts

;---------------------------------------------------------------------------
; USHORT get_view_mode(vreq)
;			a0
;	struc t ViewRequest  *vreq
; Check file buffer for binaries and return view mode (ASCII or hex dump)
;---------------------------------------------------------------------------
_get_view_mode:
	PUSH	d2

	move.l	a0,a1				; a1 := vreq ptr
	move.l	vr_Buffer(a1),a0		; a0 := buffer ptr
	move.l	vr_BufferSize(a1),d2		; d1 := size
	moveq	#VIEW_MODE_ASCII,d0		; mode = ASCII dump

gvm_loop:
	subq.l	#1,d2				; end of buffer ?
	bmi	gvm_exit
	move.b	(a0)+,d1			; get char from buffer

gvm_ascii:
	cmp.b	#' ',d1				; check for ASCII char
	blo	gvm_alternate
	cmp.b	#'~',d1
	bls	gvm_loop			; yes, its an ASCII char

gvm_alternate:
	cmp.b	#160,d1				; check for alternate char
	blo	gvm_control_code
	cmp.b	#255,d1
	bls	gvm_loop			; yes, its an alternate char

gvm_control_code:
	cmp.b	#9,d1				; tab ?
	beq	gvm_loop
	cmp.b	#10,d1				; line feed ?
	beq	gvm_loop
	cmp.b	#12,d1				; form feed ?
	beq	gvm_loop
	cmp.b	#13,d1				; carriage return ?
	beq	gvm_loop

	moveq	#VIEW_MODE_HEX,d0		; mode = hex dump

gvm_exit:
	PULL	d2
	rts

;---------------------------------------------------------------------------
; BOOL build_view_line_list(vreq, view_mode)
;			     a0       d0
;	struct ViewRequest  *vreq
;	USHORT view_mode
; Build view line list with nodes for every line of text
;---------------------------------------------------------------------------
_build_view_line_list:
	PUSH	d2-d7/a2-a3/a5-a6

	move.l	a0,a5				; a0 := vreq ptr
	move.w	d0,d7				; d7 := view mode
	move.w	d7,vr_Mode(a5)			; set mode
	move.l	(4).w,a6			; a6 := exec base
	moveq	#MLH_SIZE,d0			; alloc mem for line list header
	move.l	#MEMF_PUBLIC|MEMF_CLEAR,d1
	CALLSYS	AllocMem
	move.l	d0,vr_Display+d_List(a5)
	beq	bvl_error
	move.l	d0,a0				; init new list
	NEWLIST	a0
	move.l	vr_Buffer(a5),a3		; a3 := buffer ptr
	move.l	vr_BufferSize(a5),d6		; d6 := size
	moveq	#0,d5				; reset entry counter
	moveq	#0,d4				; reset offset counter
	move.w	_max_line_len,d3		; max num of chars per line
	subq.w	#1,d3				; dec num -> only valid for ASCII dump
	tst.w	d7				; check mode -> bytes per line
	beq	bvl_loop
	moveq	#MAX_HEX_LONG_WORDS*4-1,d3	; bytes per line for hex dump

bvl_loop:
	move.l	a3,a2				; save start of line ptr
	moveq	#0,d1				; reset pos counter -> only valid for ASCII
	moveq	#0,d2				; reset byte counter
	tst.w	d7				; check mode -> bytes per line
	beq	bvl_line_loop
	move.w	d3,d1				; reload char counter -> only valid for hex dump

bvl_line_loop:
	tst.l	d6				; end of buffer ?
	beq	bvl_next_line
	move.b	(a3)+,d0			; get char from buffer
	subq.l	#1,d6				; dec size counter
	tst.w	d7				; check mode
	beq	bvl_ascii

bvl_hex:
	addq.w	#1,d2				; inc char counter
	dbra	d1,bvl_line_loop
	bra	bvl_next_line

bvl_ascii:
	cmp.b	#10,d0				; line feed ?
	beq	bvl_next_line
	cmp.b	#12,d0				; form feed ?
	beq	bvl_next_line
	cmp.b	#13,d0				; carriage return ?
	beq	bvl_next_line
	addq.w	#1,d2				; inc char counter
	cmp.b	#9,d0				; tab ?
	bne	bvl_ascii_normal

bvl_ascii_tab:
	move.w	d1,d0				; calc num of spaces to fill
	not.w	d0
	and.w	#7,d0
	addq.w	#1,d0
	add.w	d0,d1				; add num of spaces to pos counter
	bra	bvl_ascii_check_pos

bvl_ascii_normal:
	addq.w	#1,d1				; inc pos counter

bvl_ascii_check_pos:
	cmp.w	d3,d1				; end of line ?
	bls	bvl_line_loop

bvl_next_line:
	moveq	#LineNode_Sizeof,d0		; alloc mem for line node
	move.l	#MEMF_PUBLIC|MEMF_CLEAR,d1
	CALLSYS	AllocMem
	tst.l	d0
	beq	bvl_error
	move.l	d0,a1				; a1 := line node ptr
	move.l	a2,ln_Line(a1)
	move.w	d2,ln_LineLen(a1)
	move.l	d4,ln_Offset(a1)
	ext.l	d2
	add.l	d2,d4				; update offset counter
	tst.w	d7				; view mode == ASCII ?
	bne	bvl_no_offset_inc
	add.l	#1,d4				; inc ASCII offset for LF, FF or CR

bvl_no_offset_inc:
	addq.l	#1,d5				; inc entry counter
	move.l	d5,ln_Pos(a1)
	move.l	vr_Display+d_List(a5),a0	; a0 := list ptr
	ADDTAIL					; append node to list
	tst.l	d6				; end of buffer ?
	bne	bvl_loop

bvl_end_of_buffer:
	move.l	d5,vr_Display+d_NumEntries(a5)	; save num of entries in vreq
	moveq	#1,d0				; return value -> success

bvl_exit:
	PULL	d2-d7/a2-a3/a5-a6
	rts

bvl_error:
	moveq	#0,d0				; return value -> error
	bra	bvl_exit

;---------------------------------------------------------------------------
; struct LineNode  *get_first_visible_line(vreq, old_offset)
;					    a0       d0
;	struct ViewRequest  *vreq
;	ULONG old_offset
; Get first visible line node from offset of old view mode
;---------------------------------------------------------------------------
_get_first_visible_line:
	PUSH	d2/a2

	move.l	a0,a2				; a2 := vreq ptr
	move.l	d0,d1				; d1 := old offset
	move.w	vr_Mode(a2),d2			; d2 := view mode
	move.l	vr_Display+d_List(a2),a0
	move.l	MLH_HEAD(a0),a0			; a0 := first node in list
	move.l	a0,a1				; a1 := default first visible node

gfvl_loop:
	tst.l	MLN_SUCC(a0)			; end of list ?
	beq	gfvl_not_found
	moveq	#0,d0
	move.w	ln_LineLen(a0),d0
	add.l	ln_Offset(a0),d0
	cmp.l	d1,d0				; offset + line_len > old_offset ?
	bhi	gfvl_found
	move.l	MLN_SUCC(a0),a0			; next line node
	bra	gfvl_loop

gfvl_not_found:
	move.l	a1,d0				; return first node as first visible node
	bra	gfvl_exit

gfvl_found:
	move.l	a0,d0				; return current node as first visible node

gfvl_exit:
	PULL	d2/a2
	rts

;---------------------------------------------------------------------------
; struct LineNode  *search_line_node(start_node, string, len)
;					 a0        a1    d0
;	struct LineNode  *start_node
;	UBYTE  *string
;	USHORT len
; Search line node in list which contains the search string
;---------------------------------------------------------------------------
_search_line_node:
	PUSH	d2/a2-a3/a5

	move.l	a0,d1				; start node == NULL ?
	beq	sln_not_found
	move.l	a0,a5				; a5 := lnode
	move.l	a1,a3				; a1 := search string
	move.w	d0,d2				; d2 := len of search string
	subq.w	#1,d2				; dec len for dbra

sln_search_loop:
	tst.l	MLN_SUCC(a5)			; end of list ?
	beq	sln_not_found

	move.l	ln_Line(a5),a2			; a2 := line ptr
	move.w	ln_LineLen(a5),d1		; d1 := line len
	bsr	compare_lines
	tst.b	d0
	beq	sln_found
	move.l	MLN_SUCC(a5),a5			; next node
	bra	sln_search_loop

sln_found:
	move.l	a5,d0				; d0 := file node -> return value
	bra	sln_exit

sln_not_found:
	moveq	#0,d0				; d0 := NULL -> return value

sln_exit:
	PULL	d2/a2-a3/a5
	rts

compare_lines:	; (a2 := line ptr, a3 := search string ptr, d1.w := line len, d2.w := search string len - 1)
	move.b	(a3),d0				; search first char of search string

cl_loop1:
	cmp.b	(a2)+,d0
	beq	cl_check_full
	subq.w	#1,d1				; end of line ?
	bhi	cl_loop1

cl_no_match:
	moveq	#1,d0				; search string not found
	rts

cl_check_full:
	move.l	a2,a0				; check full search string
	lea	1(a3),a1
	move.w	d2,d0				; only one char to search ?
	beq	cl_match
	subq.w	#1,d0

cl_loop2:
	cmp.b	(a0)+,(a1)+
	bne	compare_lines
	dbra	d0,cl_loop2

cl_match:
	moveq	#0,d0				; search string found
	rts

;---------------------------------------------------------------------------
; UBYTE *protection(fnode)
;		     a0
;	struct FileNode  *fnode
; Build string with protection bits setting for given file node
;---------------------------------------------------------------------------
_protection:
	PUSH	d2/a2

	move.l	a0,a2				; a2 := fnode ptr
	move.w	fn_Protection(a2),d0
	move.l	_protection_bits,a0
	lea	_protection_string,a1
	moveq	#MAX_PROTECTION_BITS-1,d2	; d2 := bit count - 1
	clr.b	1(a1,d2.w)			; set end of string

prot_loop:
	moveq	#'-',d1				; default value for not active
	cmp.w	#MAX_PROTECTION_BITS-4,d2	; RWED ?
	blo	prot_chspa

prot_rwed:
	lsr.w	#1,d0				; RWED : protection bit clear ?
	bcs	prot_write_byte
	bra	prot_get_byte

prot_chspa:
	lsr.w	#1,d0				; CHSPA : protection bit set ?
	bcc	prot_write_byte

prot_get_byte:
	move.b	(a0,d2.w),d1

prot_write_byte:
	move.b	d1,(a1,d2.w)
	dbra	d2,prot_loop
	move.l	a1,d0				; return ptr to protection string

	PULL	d2/a2
	rts

;---------------------------------------------------------------------------
; VOID build_limited_path_name(buffer,device_name,path_name,max_len,mode)
;			         a0        a1         a2       d0    d1
;	BYTE   *buffer, *device_name, *path_name
;	USHORT max_len, mode
; Build path name with specified maximal length - if path is too long then
; use the form 'start of path...end of path'
;---------------------------------------------------------------------------
_build_limited_path_name:
	PUSH	d2-d5/a2-a3

	; --- init some regs
	move.l	a0,a3			; a3 := buffer
	move.w	d0,d2			; d2 := max len
	move.w	#0,d3			; d3 := buffer char count
	move.w	d1,d4			; d4 := mode

	; --- copy device name (if any) to buffer
	move.l	a1,d0
	beq	blpn_count_path_name_len

bpln_copy_device_name:
	move.b	(a1)+,d0
	beq	bpln_skip_volume_name
	move.b	d0,(a3)+
	addq.w	#1,d3			; inc char count
	bra	bpln_copy_device_name

bpln_skip_volume_name:
	; --- skip volume name in path name
	sub.w	d3,d2			; max len -= char count

bpln_skip_loop:
	cmp.b	#':',(a2)+
	bne	bpln_skip_loop

blpn_count_path_name_len:
	; --- count length of path name
	move.l	a2,a0			; a0 := path name
	moveq	#0,d5			; d5 := len count

bpln_count_loop:
	tst.b	(a0)+
	beq	bpln_check_path_name_len
	addq.w	#1,d5			; inc len count
	bra	bpln_count_loop

bpln_check_path_name_len:
	; --- compare path name len with max name len
	cmp.w	d5,d2
	bgt	bpln_build_full_path_name

	; --- build limited path name
	moveq	#START_OF_PATH_NAME_LEN,d0
	sub.w	d3,d0			; d0 := len of 'start of path name'
	sub.w	d0,d2			; max len -= len of 'start of path name'
	sub.w	d0,d5			; path name len -= len of 'start of path name'
	subq.w	#1,d0			; dec for dbra

bpln_copy_start_of_path_name:
	; --- copy 'start of path name' to buffer
	move.b	(a2)+,(a3)+
	dbra	d0,bpln_copy_start_of_path_name

	; --- insert delimiters
	moveq	#'.',d0
	move.b	d0,(a3)+
	move.b	d0,(a3)+
	move.b	d0,(a3)+
	subq.w	#3,d2			; dec max len

	; --- copy end of path name to buffer
	lea	0(a2,d5),a0		; a0 := last char of path name
	sub.w	d2,a0			; a0 := start of 'end of path name'
	subq.w	#1,d2			; dec for dbra
	
bpln_copy_end_of_path_name:
	move.b	(a0)+,(a3)+
	dbra	d2,bpln_copy_end_of_path_name
	bra	bpln_exit

bpln_build_full_path_name:
	; --- copy full path name to buffer
	sub.w	d5,d2			; max_len -= path name len
	subq.w	#1,d5			; dec for dbra
	bmi	bpln_fill_rest_of_buffer

bpln_copy_loop:
	move.b	(a2)+,(a3)+
	dbra	d5,bpln_copy_loop

bpln_fill_rest_of_buffer:
	; -- fill rest of buffer with spaces
	tst.w	d4			; no fill?
	beq	bpln_exit
	moveq	#' ',d0
	subq.w	#1,d2			; dec for dbra

bpln_fill_loop:
	move.b	d0,(a3)+
	dbra	d2,bpln_fill_loop

bpln_exit:
	; --- mark end of string
	clr.b	(a3)

	PULL	d2-d5/a2-a3
	rts
