//
// DIR-FDOUBLE.VDM Christian Ziemski 09.03.2004
// 19.03.2004
//
// Helper tool for finding double files per name in a directory tree.
//
// Only for WINNT and above yet (due to DIR format)
// (And only tested with German Windows yet.)
//
//
//
//----- Online documentation (via Shift-F1) between tags and below ---
//
//----- Please note: Due to the proportional font of DI_1() it is necessary
// to use manual TAB's in the following documentation block!
// (And here and there a space for finetuning...)
//
// DIR-FDOUBLE Helper tool for finding double files (C) Ch.Ziemski 3/2004
//
// This macro lists all files with double filenames found in a directory tree.
// The following data is shown for each file: name, date, time, size and path.
//
// Provides these commands via key assignment to work with the files:
//
// F12 - open file at cursor line
// - close the file again
//
// Shft-F12 - open files with same name in and around cursor line
// - closes all same named files again
//
// Ctrl-F12 compare files with same name in and around cursor line
// (And marks them with A..J to show diffs)
//
// Ctrl-F10 - toggle DELETE flag of file in current cursor line
// - toggle DELETE flag of current file in list
// Ctrl-Shft-F10 ... and advance one line
//
// Alt-F10 delete non double filenames
//
// F11 sort list by filename
// Shft-F11 sort list by path
// Ctrl-F11 sort list by DELETE flag (a '#') at end of file
// Alt-F11 sort list by file size
//
//
// Shft-F1 this help
//--
//
//
// This macro is still under development.
//
// If you have any comments, please let me know.
//
// Christian
//
//
//
//----------------------------------------------------------------------------------
//
// To do:
//
// - test and support more Windows versions
// - input parameter for drive/path as alternative for DI_1()
// - more comfort and tools
// - Tools menu additional to the key assignments
// - UNC pathnames? how to check Cur_Col then?
// - Win_Split()/Win_Create() error handling
// - generally more error handling
// - clean up and document register usage
// - sub titles for paragraphs (//--) like in DI1-PAGER
// - Config(CM_E_UNDO,0) doesn't always work!?
//
//--------------------------------------------------------------------------
//
// #90 reg num of running macro
// #91 file list loaded into a free buffer
// #92 win_width
// #93 win_height
// #94 flag for formatting empty lines in filename-sort
//
// #103
// #104
// #105
//
// @103
// @104
// @105
// @106
//--------------------------------------------------------------------------
if ( ! Is_Windows) {
Statline_Message("Sorry, this macro requires VEDIT for Windows.")
return
}
#94=1 // insert an empty line after each filename-block in name sort
Key_Add("Shft-F1" , "[VISUAL EXIT] Call(#90, 'HELP')",OK)
Key_Add("F11" , "[VISUAL EXIT] Call(#90, 'SORTBYNAME') if (#94){Call(#90, 'INSERTDIVIDER')}", OK)
Key_Add("Shft-F11", "[VISUAL EXIT] Call(#90, 'SORTBYPATH')", OK)
Key_Add("Ctrl-F11", "[VISUAL EXIT] Call(#90, 'SORTBYFLAG')", OK)
Key_Add("Alt-F11" , "[VISUAL EXIT] Call(#90, 'SORTBYSIZE')", OK)
Key_Add("F12" , "[VISUAL EXIT] Call(#90, 'OPENFILE')", OK)
Key_Add("Shft-F12", "[VISUAL EXIT] Call(#90, 'OPENFILES')", OK)
Key_Add("Ctrl-F12", "[VISUAL EXIT] Call(#90, 'COMPAREFILES')", OK)
Key_Add("Ctrl-F10" , "[VISUAL EXIT] #104=0 Call(#90, 'TOGGLEDEL')", OK)
Key_Add("Ctrl-Shft-F10", "[VISUAL EXIT] #104=1 Call(#90, 'TOGGLEDEL')", OK)
Key_Add("Alt-F10" , "[VISUAL EXIT] Call(#90, 'SORTBYNAME') Call(#90, 'DELNONDOUBLE') if (#94){Call(#90, 'INSERTDIVIDER')}", OK)
#103=File_Check("|(VEDIT_TEMP)/dir.out")
if (#103 != -1) {
Buf_Switch(#103)
Buf_Quit(OK)
Update
}
#90 = Macro_Num //#90 = reg# of running macro
Reg_Set(103, HOME) // default directory name
Call("GETDIR")
Config( D_H_CR_LINE, "Highlight cursor line (1=Full, 2=Partial)", 1 )
if (File_Size > 0) {
Buf_Switch(Buf_Free)
}
#91=Buf_Num
#92=Win_Width
#93=Win_Height
Reg_Set(104, "|(VEDIT_TEMP)/dir.out") // save into this target file
Message("getting directory listing...", STATLINE)
Call("SHELLOUT") // Shell out with "dir" command to create DIR.OUT
Buf_Switch(#91)
File_Open("|(VEDIT_TEMP)/dir.out",NOEVENT)
Update
Call("MAKEDIRLIST") // Clean up "dir" output
Update
Call("LINEUP") // format the list with equally sized file names
Update
Call("SORTBYNAME") // sort list according file names
Update
Call("DELNONDOUBLE") // delete lines with single files
Update
if (#94) {
Call("INSERTDIVIDER")
}
return
/////////////////////////////////////////////////////////////////////////////
//
// SUBROUTINES
//
//---------------------------------------------------------------------------
//
// LINEUP
//
:LINEUP:
Message("formatting directory listing...", STATLINE)
#103=0
BoF
while (! At_EOF) {
Search("::", NOERR+ERRBREAK)
#103 = Max(#103, Cur_Col)
Line(1, NOERR)
}
BoF
while (! At_EOF) {
Search("::", NOERR+ERRBREAK)
Del_Char(2)
Ins_Char(32, COUNT, #103-Cur_Col+4)
}
return
// --------------------------------------------------------------------------
//
// DELNONDOUBLE
//
:DELNONDOUBLE:
// delete lines with non double filenames in a sorted text
Message("extracting doubles ...", STATLINE)
BoF
Search(" |A:\", NOERR)
if (EM) {
return
}
BB(CLEAR)
#103=Cur_Col-42 // a character before the date column
BoF
RCB(104, BoL_Pos, BoL_Pos+#103)
Line(1, NOERR)
#104=1
repeat (ALL) {
if (Match(@104)==0) {
#104++
} else {
if (#104==1) {
Del_Line(-1)
} else {
#104=1
}
RCB(104, BoL_Pos, BoL_Pos+#103)
}
Line(1, NOERR+ERRBREAK)
}
BoF
return
// --------------------------------------------------------------------------
//
//
// SHELLOUT - Shell out, using directory name in @103 and target filename in @104
// With long filenames, need to shell out with command such as:
// dir "filespec" /one
// Else, shell out with command such as:
// dir filespec /one
//
:SHELLOUT:
if (Is_Longfilename) { //Need double-quotes around filespec
Reg_Set(105,'dir "')
Reg_Set(105,@103, APPEND)
Reg_Set(105,'"', APPEND)
} else {
Reg_Set(105,'dir ')
Reg_Set(105,@103, APPEND)
}
Reg_Set(105,' /s', APPEND) // need the "/s" option
Reg_Set(105,' /one >', APPEND) // Sort by name and then extension
//
// Add redirection filename; long filename needs double-quotes
//
if (Is_Longfilename) { //Need double-quotes around filespec
Reg_Set(105,' "', APPEND)
}
Reg_Set(105, @104, APPEND)
if (Is_Longfilename) { //Need double-quotes around filespec
Reg_Set(105,'"', APPEND)
}
//
// Shell out with "dir" command to create a sorted list of files:
// The syntax with single AND double quotes is necessary because of
// long filenames (strange but true) ...
// The difference between NT/2000/XP and W9x must be handled carefully!
//
if (Is_WinNT) {
Sys('"|@(105)"',DOS+SIMPLE+OK+SUPPRESS)
} else {
Sys('|@(105)',DOS+SIMPLE+OK+SUPPRESS)
}
return()
// --------------------------------------------------------------------------
//
// MAKEDIRLIST - removes: - header and footer
// - subdirectory entries
// from DIR output (in English and German Windows)
//
:MAKEDIRLIST:
Message("processing directory listing...", STATLINE)
BOF()
Search("|{directory|wof,Verzeichnis|wvon}|W",ADVANCE+NOERR)
BOL()
Del_Line(-ALL) //Delete Header info
Replace("^.*
.*\N", "", REGEXP+BEGIN+ALL+CASE+NOERR)
BOF()
repeat (ALL) {
Search("|<|{Total Files Listed:,Dateien gesamt:}",ERRBREAK)
Del_Line(0); Del_Line(1)
}
Replace("|< |Y|N","",BEGIN+ALL+NOERR) //Delete info lines beginning with spaces
Replace("|<|N","", BEGIN+ALL+NOERR) //Delete empty lines
//
// Now do the subdirs
//
BOF() //loop through every directory block
Search("|{directory|Wof,Verzeichnis|Wvon}|W",ADVANCE+NOERR)
while (!Error_Match) {
Reg_Copy_Block(17,Cur_Pos, EoL_Pos) //complete pathname
BOL()
Del_Line(1) //delete info line
if (At_EOF) { Break }
while (Match("|[|W]|{directory|Wof|W,verzeichnis|Wvon|W}") != 0) {
Goto_Col(40) // ?
Reg_Copy_Block(18, Cur_Pos, EoL_Pos)
Ins_Text(" ")
Reg_Ins(17)
Ins_Text("\")
BoL
Reg_Ins(18)
Ins_Text(" :: ")
Line(1,NOERR+ERRBREAK)
if (At_EoF) {
break
}
}
Char(Chars_Matched)
if (At_EOF) { Break }
}
return
// --------------------------------------------------------------------------
//
// OPENFILE - opens the file in the current cursor line
// (if in buffer with file list;
// else close current file and switch to list)
:OPENFILE:
if (Buf_Num == #91) {
Save_Pos()
Search_Block(" |A:\", BoL_Pos, EoL_Pos, NOERR)
if (EM) {
Restore_Pos()
return
}
Reg_Copy_Block(106, Cur_Pos+1, EoL_Pos)
Restore_Pos()
File_Open("|@(106)")
} else {
Buf_Quit(DELETE)
Buf_Switch(#91)
// Buf_Switch(#91, ATTACH)
// Win_Switch(Win_Next(BUFFER)) // ???
// #77=Win_Num
// Win_Move(Win_Num,0,0,#92, #93) // geht so nicht!? Falsches Window wird vergrössert.
// Win_Move(#91,0,0,#92, #93) // unsauber
}
return
// --------------------------------------------------------------------------
//
// OPENFILES - opens the files with the same filename like the file in the
// current cursor line (list must be sorted by filename).
// (if in buffer with file list;
// else try to close current file and all with that name
// and then switch to list)
//
:OPENFILES:
if (Buf_Num == #91) {
Save_Pos()
Search_Block(" |A:\", BoL_Pos, EoL_Pos, NOERR)
if (EM) {
Restore_Pos()
return
}
Char(-42)
#103=Cur_Col // a character before the date column
Search("|!|W", REVERSE+ADVANCE)
Reg_Copy_Block(105, BoL_Pos, Cur_Pos) // filename
Line(-1, NOERR)
if (! EM) {
while (Match(@105)==0) {
Line(-1, NOERR+ERRBREAK)
}
if (! At_BoF) {
Line(1)
}
}
#104=-1 // # of first new opened buffer
Block_Begin(Cur_Pos)
while (Match(@105)==0) {
Goto_Col(#103+43)
Reg_Copy_Block(106, Cur_Pos, EoL_Pos)
if (File_Check(@106) == -1) {
#105=Buf_Free
if (#104==-1) {
//Win_Split(#105, Win_Lines-10, BOTTOM)
Win_Move(Win_Num, Win_X_Org, Win_Y_Org, Win_Width, 10*Font_Height)
Win_Create(#105, Win_Y_Org+10*Font_Height, Win_X_Org, #93-10*Font_Height, #92, PIXEL)
} else {
Win_Split(#105, 0, RIGHT) // problematic if not enough screen width
}
Buf_Switch(#105)
}
File_Open("|@(106)")
if (#104==-1) {
#104=Buf_Num
}
Win_Attach(#105)
Win_Switch(#105)
Update // why necessary?
Buf_Switch(#91)
Line(1, NOERR+ERRBREAK)
}
Block_End(Cur_Pos)
Restore_Pos()
Set_Visual_Line(0)
Buf_Switch(#104)
} else {
Reg_Set(103, FILENAME)
if (Reg_Size(103)>0) {
#103=1
do {
Buf_Switch(#103)
if (#103 != #91) {
if (Buf_Status(#103) > -1) {
Reg_Set(104, FILENAME)
if (Reg_Compare(103, @(104)) == 0) {
Buf_Quit(DELETE) // incl. delete attached windows
}
}
}
#104=#103
#103 = Buf_Next
} while(#103 > #104)
}
Buf_Switch(#91)
//Win_Move(Win_Num,0,0,(Screen_Cols+1)*Font_Width, (Screen_Lines+1)*Font_Height)
Win_Move(Win_Num,0,0,#92, #93)
}
return
// --------------------------------------------------------------------------
//
// SORTBYNAME - sort the list by filename
//
:SORTBYNAME:
if (Buf_Num == #91) {
Message("sorting ...", STATLINE)
Replace("|<|[|W]|N", "", BEGIN+ALL+NOERR) // remove empty lines
BoF
Search(" |A:\", NOERR)
if (EM) {
return
}
BB(CLEAR)
#103=Cur_Col-42 // a character before the date column
Sort_Merge("1,#103", 0, FileSize)
BoF
}
return
// --------------------------------------------------------------------------
//
// SORTBYPATH - sort the list by pathname
//
:SORTBYPATH:
if (Buf_Num == #91) {
Message("sorting ...", STATLINE)
Replace("|<|[|W]|N", "", BEGIN+ALL+NOERR) // remove empty lines
Message("Processing... (About 30 sec / Megabyte) -- Press to abort ",STATLINE)
#104=0
Begin_Of_File()
while (! AT_EOF) {
EOL()
#104=Max(#104, Cur_Col)
Line(1,ERRBREAK)
}
BoF
Search(" |A:\", NOERR)
if (EM) {
return
}
BB(CLEAR)
Sort_Merge("Cur_Col+1,#104", 0, FileSize)
BoF
}
return
// --------------------------------------------------------------------------
//
// SORTBYSIZE - sort the list by filesize
//
:SORTBYSIZE:
if (Buf_Num == #91) {
Message("sorting ...", STATLINE)
Replace("|<|[|W]|N", "", BEGIN+ALL+NOERR) // remove empty lines
BoF
Search(" |A:\", NOERR)
if (EM) {
return
}
BB(CLEAR)
Search("|!|W", REVERSE)
#104=Cur_Col+1
Search("|W", REVERSE)
Search("|!|W", REVERSE)
#103=Cur_Col+1
Sort_Merge("#103,#104", 0, FileSize)
BoF
}
return
// --------------------------------------------------------------------------
//
// SORTBYFLAG - sort the list by flag and pathname
//
:SORTBYFLAG:
if (Buf_Num == #91) {
Message("sorting ...", STATLINE)
Replace("|<|[|W]|N", "", BEGIN+ALL+NOERR) // remove empty lines
Message("Processing... (About 30 sec / Megabyte) -- Press to abort ",STATLINE)
#104=0
Begin_Of_File()
while (! AT_EOF) {
EOL()
#104=Max(#104, Cur_Col)
Line(1,ERRBREAK)
}
BoF
Search(" |A:\", NOERR)
if (EM) {
return
}
BB(CLEAR)
Sort_Merge("Cur_Col-1,#104", 0, FileSize)
EoF
}
return
// --------------------------------------------------------------------------
//
// INSERTDIVIDER - inserts an empty line after each filename-group
//
:INSERTDIVIDER:
Replace("|<|[|W]|N", "", BEGIN+ALL+NOERR) // remove empty lines
BoF
Search_Block(" |A:\", BoL_Pos, EoL_Pos, NOERR)
if (EM) {
return
}
Char(-42)
#103=Cur_Col // a character before the date column
while (! At_EoF) {
Search("|!|W", REVERSE+ADVANCE)
Reg_Copy_Block(105, BoL_Pos, Cur_Pos) // filename
BoL
while (Match("|@(105) ") == 0) {
Line(1, NOERR+ERRBREAK)
}
Ins_Newline(1)
Goto_Col(#103)
}
BoF
return
// --------------------------------------------------------------------------
//
// TOGGLEDEL - toggle the DELETE-Flag
// if in list: toggle file in cursor line
// if in open file: toggle that file in list
//
:TOGGLEDEL:
if (Buf_Num == #91) {
Save_Pos()
BoL
Search(" |A:\", NOERR)
if (EM) {
Restore_Pos()
return
}
Char(-1)
if (Match("#") == 0) {
Ins_Char(32, OVERWRITE)
} else {
Ins_Char(35, OVERWRITE)
}
Restore_Pos()
if(#104==1) {
Do_Visual("\CD\")
}
} else {
#103=Buf_Num
Reg_Set(103, PATHNAME)
Buf_Switch(#91)
Search(@103, BEGIN+NOERR)
if (EM) {
Buf_Switch(#103)
return
}
Char(-2)
if (Match("#") == 0) {
Ins_Char(32, OVERWRITE)
} else {
Ins_Char(35, OVERWRITE)
}
Buf_Switch(#103)
}
return
// --------------------------------------------------------------------------
//
// GETDIR - user input dialog for the directory to be checked
//
// to do: more than 10 files
:GETDIR:
Reg_Set(104, "Please enter the directory to be checked (including it's subdirectories)")
repeat (ALL) { //LOOP until valid directory
#103=Dialog_Input_1(103,"`Double File Checker`,
`|@(104)
`,
`??`,`[&OK]`, `[&Help]`, `[&Cancel]`", @103,APP+CENTER,0,0)
if ((#103 == 0) || (#103 > 2)) { Break_Out(EXTRA) }
if (#103 == 2) {
Call("HELP")
Continue
}
Reg_Set(104,"Directory invalid or doesn't exist. Try again.")
if (Reg_Size(103) < 2) { continue }
if (File_Exist(@103,NOERR) != 0) { Break }
}
return()
// --------------------------------------------------------------------------
//
// COMPAREFILES
//
:COMPAREFILES:
if (Buf_Num == #91) {
Save_Pos()
Search_Block(" |A:\", BoL_Pos, EoL_Pos, NOERR)
if (EM) {
Restore_Pos()
return
}
Char(-42)
#103=Cur_Col // a character before the date column
Search("|!|W", REVERSE+ADVANCE)
Reg_Copy_Block(105, BoL_Pos, Cur_Pos) // filename
Line(-1, NOERR)
if (! EM) {
while (Match(@105)==0) {
Line(-1, NOERR+ERRBREAK)
}
if (! At_BoF) {
Line(1)
}
}
#97=70 // number of same files + 69
while (Match(@105)==0) {
Goto_Col(#103+43)
Reg_Copy_Block(106, Cur_Pos, EoL_Pos)
File_Open("|@(106)", NOEVENT)
#@97=Buf_Num // save buffer numbers of opened files in #70-#79 // to do: mark already open files with +1000 or so
Buf_Switch(#91)
Line(1, NOERR+ERRBREAK)
#97++
if (#97 > 79) { // only 10 files yet
break
}
}
#97-- // max reg.
if (#97 == 70) { // if only one file
Restore_Pos()
return
}
for (#104=80; #104<=(#97+10); #104++) {
#@104=#104-80 // set #80-#89 to 0..9
}
#98=70
#99=71
repeat (ALL) {
repeat (ALL) {
#95=#@98
#96=#@99
// .#98 #99
Buf_Switch(#95)
BoF
#106=File_Size
Buf_Switch(#96)
BoF
if (#106 == File_Size) { // if same size
if (Compare(#95+BUFFER, CASE) == 0) { // if at least one character matched
#106=0
if (!At_EoF) { // not all identical
#106=1
}
} else {
#106=1
}
} else {
#106=1
}
if (#106 == 0) { // if identical
#104=#99+10
#105=#98+10
#@104=#@105 // set to 0..9 from 1.st field
}
#99++ // 2nd pointer++
if (#99 > #97) {
#99=#98+2
break
}
}
#98++ // 1st pointer++
if (#98 == #97) {
break
}
}
for (#104=70; #104<=#97; #104++) { // mark in list and close all compared files
// to do: let opened files open
Buf_Switch(#@104)
Reg_Set(104, PATHNAME)
Buf_Switch(#91)
Search(@104, BEGIN)
Char(-3)
#105=#104+10
Ins_Char(#@105+65, OVERWRITE)
Buf_Switch(#@104)
Buf_Quit(OK)
}
Restore_Pos()
Buf_Switch(#91)
}
return
// --------------------------------------------------------------------------
// HELP - Show some help instructions (the inline doco from top of the macro)
//
// Used register (restored at end)
//
// #103 longest line
// button number
// #104 longest paragraph
// #105 tmp: line num
// #106 original buffer num
// #107 counter for paragraph
//
// @103 title line for DI_1()
// @104 button label
// @105 text of one paragraph
// @106 line of dashes
//
:HELP:
Num_Push(103,107) // save used registers
Reg_Push(103,107)
Key_Purge()
#106=Buf_Num
Buf_Switch(Buf_Free(EXTRA))
Reg_Ins(Macro_Num)
Config(CM_E_UNDO,0) // doesn't always work!?
BoF
Search("//|[|W]|H3CDOC>", NOERR+ADVANCE) // the leading "<" is masked out intentionally!
if(EM){
Message("\n\n No inline help found.", STATLINE)
}else{
Reg_Copy_Block(103, Cur_Pos, EoL_Pos)
Line(1)
Del_Line(-ALL)
Search("//|[|W]|H3C/DOC>", NOERR)
if(!EM){
BoL
Del_Block(Cur_Pos, File_Size)
}
Replace("^/+", BEGIN+ALL+REGEXP+NOERR) // remove leading comment characters
BoF
while(! At_EoF){ // shorten lines longer than, say 100 (must be improved!!!!, perhaps wrapping?)
Goto_Col(100)
Del_Block(Cur_Pos, EoL_Pos)
Line(1, NOERR+ERRBREAK)
}
}
// For security; to prevent DI1() from beeing killed - but not foolproof!
Replace("`","'", BEGIN+ALL+NOERR)
Replace("||","!", BEGIN+ALL+NOERR)
// find longest line
#103=0
BoF
while (! At_EoF) {
EoL
#103=Max(#103, Cur_Col)
Line(1, NOERR)
}
// Create a line of underscores of (hopefully) appropriate length
// to size the dialog's width.
// (Due to the used proportional font it's only guessing!)
BoF
Ins_Char(95, COUNT, Max(37,#103))
Ins_Newline(1)
BoF
Reg_Copy(106, 1)
Del_Line(1)
// find longest "paragraph" ==> #104
BoF
#104=0
#105=1
while (! At_EoF) {
Search("|<--", NOERR)
if (EM) {
EoF
}
#104=Max(#104, Cur_Line - #105)
Line(1, NOERR+ERRBREAK)
#105=Cur_Line
}
// make all "paragraphs" equal length
BoF
#105=1
while (! At_EoF) {
Search("|<--", NOERR)
if (EM) {
EoF
} else {
BoL
Del_Line(1)
}
if ((Cur_Line - #105 + At_EoF) < #104) {
Ins_Newline(#104 - Cur_Line + #105)
}
#105=Cur_Line
}
BoF
// try to format via TABs and change TAB's to \t for DI_1()
Retab_Block(0, Filesize)
Replace("|009", "\t", BEGIN+ALL+NOERR)
BoF
Key_Purge()
// scroll through the text
Reg_Set(104, "&Next") // [Next/End] button button label
Reg_Set(107, "[( Previous )]") // [Previous] button label
#107=1 // counter for paragraph
while (! At_EoF) {
Reg_Copy(105,#104)
#105=Cur_Line
Line(#104, NOERR)
if (At_EoF) { // change the button label if at EoF
Reg_Set(104, "E&nd")
}
#105=Cur_Line - #105 // remember the number of lines gone forward
#103=Dialog_Input_1(103,^`|@(103)`,
`|@(105)`,
`|@(106)`,
`|@(107)`,`.b[|@(104)]`,`[&Cancel]`^,SET+APP+CENTER,0,0)
if (#103 == 1) { // [Previous]
Reg_Set(104, "&Next")
#107--
Line(-#104-#105, NOERR)
if (#107<=1) {
#107=1
Reg_Set(107, "[( Previous )]") // button label
}
continue
}
if (#103 != 2) { // not [Next]
break
}
#107++
Reg_Set(107, "[&Previous]") // button label
}
Buf_Quit(OK)
Buf_Switch(#106)
Reg_Pop(103,107) // restore used registers
Num_Pop(103,107)
Key_Purge()
return