Select all files/folders in expanded folders that have selected files/folders and below

I made a new variation on selecting files/folders in expanded folders.
This variant selects all files and folders in expanded folders that have any selected files/folders.

I think I have this working within normal file paths and collections, but it still doesn't work within libraries. I commented that part out of the button.
The button has to be set as an Evaluator Function.

arr = GetItems("s");
if( len( arr ) == 0 )
     {
	 output( "Empty Array" );
	 return;
	 }
for( i = 0 ; i < len( arr ); i++ )
     {
     selpath = ArrayGet( arr , i );
	 IsPath( selpath ) ?  : return;
	 if( selpath ~~ "\" )
	      {
		  num = InStr( selpath,"\", "r");		  
          newpath = Left( selpath , num + 1);
		  ArraySet( arr , i , newpath + "*" );		  	  
		  output( selpath );
		  output (newpath);
	      }
	 //elseif( selpath ~~ "/" )
	      //{
          //num = InStr( selpath,"/", "r");		  
          //newpath = Left( selpath , num + 1 );
		  //ArraySet( arr , i , newpath + "*" );		  	  
		  //output( selpath );
		  //output (newpath);
		  //}
     else return;	 
	 }

for( i = 0 ; i < len( arr ); i++ )
     {
	 nested = """" + ArrayGet( arr, i ) + """";
	 nested = "Select Filterdef fullpath match " + nested;
	 nested = EscapeWild( nested, "b" );
	 output( nested );
     Run( nested );  
     }

Select Nested.dcf (2.4 KB)

Well, even if it sounds contradictory, IMO this kind of thing is easier to do via scripting, since you have more options and utilities.

I think this works even in collections. Probably not in libraries.

What it does is get the immediate parent of the selected item, filter out duplicates, and then create an instruction for the filter.
Let me know if you need it to work in libraries too.

items = GetItems("s");
n_items = len(items);
if (n_items == 0) return;
parents = ArrayCreate();
for (i = 0; i < n_items; i++) {
	item = ArrayGet(items, i);
	parent = Parent(item);
	if (parent == source) continue;
	ArrayPush(parents, parent);
}
n_items = len(parents);
if (n_items == 0) return;
last = "";
ArraySort(parents);
unique_parents = ArrayCreate();
for (i = 0; i < n_items; i++) {
	item = ArrayGet(parents, i);
	if (last == item) {
		Output(item + " is dupe");
		continue;
	}
	ArrayPush(unique_parents, EscapeWild(item, "b"));
	last = item;
}
prt = len(unique_parents) > 1;
cmd = "Select FILTERDEF fullpath match """;
if (prt) cmd += "(";
cmd += Implode(unique_parents, "|");
if (prt) cmd += ")";
cmd += "\\*""";
Output(cmd);
Run(cmd);

Thanks !
As obvious as it was, I didn't think to use Parent() .
That helps much to simplify the code.

I did think of using ArraySort() to find dupes.
I hadn't written that part yet as it took me quite some time to get Run() to work.
Also, I ran Run() multiple times rather than just once as you did.

This doesn't work in libraries, but it does work in file collections .
I think this works well enough as it is now.

This one should work with libraries too (or at least in most scenarios). It will also correctly escape wildcard characters (it looks like you need to escape them twice).

items = GetItems("s");
n_items = len(items);
if (n_items == 0) return;
is_lib = PathType(source) == "lib";
Output("is lib:" + is_lib);
parents = ArrayCreate();
for (i = 0; i < n_items; i++) {
	item = ArrayGet(items, i);
	parent = Parent(item);
	if (parent == source) continue;
	if (is_lib) {
		Output("Resolving to true path..");
		parent = DisplayName(parent, "r");
	}
	Output("item :" + item + "; parent :" + parent);
	ArrayPush(parents, parent);
}
n_items = len(parents);
if (n_items == 0) return;
last = "";
ArraySort(parents);
unique_parents = ArrayCreate();
for (i = 0; i < n_items; i++) {
	item = ArrayGet(parents, i);
	if (last == item) {
		Output(item + " is dupe");
		continue;
	}
	ArrayPush(unique_parents, EscapeWild(EscapeWild(item, "b")));
	last = item;
}
prt = len(unique_parents) > 1;
cmd = "Select FILTERDEF fullpath match """;
if (prt) cmd += "(";
cmd += Implode(unique_parents, "|");
if (prt) cmd += ")";
cmd += "\\*""";
Output(cmd);
Run(cmd);

I see one bug.
Open a lister to a Dive Path.
Select a file/folder in that path.
My earlier version works, but neither of yours do.
I'll look into it tomorrow.

I though I had that right.
But, the structure has changed and for the good.
I'll have to think on that some as to what is happening.

Edit: Or it is a bug in mine.

Yes, that is correct. I had been using file names that included parentheses for testing, but I neglected to use a filename whose parent file name also contained parentheses .
Good catch !
Yep, it needs EscapeWild(EscapeWild(item, "b"))) rather than (EscapeWild(item, "b")) .

I see now that you bypassed this with if (parent == source) continue; as it it simplifies counting \ characters in the final Run command. Also it really isn't needed because that case simplifies to Select All. However, when playing with this button it is fun to see selection mistakes become this.

So FWIW, here is my one line quick fix .

items = GetItems("s");
n_items = len(items);
if (n_items == 0) return;
is_lib = PathType(source) == "lib";
Output("is lib:" + is_lib);
parents = ArrayCreate();
for (i = 0; i < n_items; i++)
    {
	item = ArrayGet(items, i);
	parent = Parent(item);
	//if (parent == source) continue;	// Alternative line 17 .
	if (is_lib) 
	    {
		Output("Resolving to true path..");
		parent = DisplayName(parent, "r");
    	}
	if (parent == source) parent = Truncate( parent, 2 , 0 );	   // Alternative line 11 .
	ArrayPush(parents, parent);
    }
n_items = len(parents);
if (n_items == 0)  return;
last = "";
ArraySort(parents);
unique_parents = ArrayCreate();
for (i = 0; i < n_items; i++) 
    {
	item = ArrayGet(parents, i);
	if (last == item) 
	    {
		Output(item + " is dupe");
		continue;
	    }
	ArrayPush(unique_parents, EscapeWild(EscapeWild(item, "b")));
	last = item;
    }
prt = len(unique_parents) > 1;
cmd = "Select FILTERDEF fullpath match """;
if (prt) cmd += "(";
cmd += Implode(unique_parents, "|");
if (prt) cmd += ")";
cmd += "\\*""";
Output(cmd);
Run(cmd);

Do you have a specific case where the bug can be reproduced?

Because it only converts to the real path for items in libraries, but in that case source should also be something like lib:\\xyz, so I don't see how the second comparison in your code would make a difference.

What I called a bug was parent paths that are drive letters, C:\ for instance.
You simply bypassed that case with if (parent == source) continue; , which is fine.
This really isn't a bug, it is a special case that really isn't needed because it would result in Select All.

My thought was to remove the trailing \ in this case with
if (parent == source) parent = Truncate( parent, 2 , 0 ); so that C:\ for instance,
would become C: .
I put this line after the if(is_lib) clause because even though the parent of lib://Documents for instance would be Libraries and the source is lib://, parent == source is still true.

My line if (parent == source) parent = Truncate( parent, 2 , 0 ); if placed before the DisplayName(parent, "r") clause then turns Libraries into Li .

Yeah, in those cases there would be an extra slash. But I don't think you could realistically find a case where you'd get C:\ as an expanded folder. I honestly can't think of how you'd even manage that.

Believe it or not, I don't think I've ever used libraries. After enable them to actually test the script, I noticed it doesn't work, for example, if you select Documents inside libraries, expand it, and then select the first item and use the code from above. I think that's because there's a difference between what the fullpath column shows and the actual value used internally in filters.

To fix that, you'd probably need to use scripting, or do it in the Evaluator with a much longer piece of code where you'd also have to work with the array of all the items, not just the selected ones. Then you'd need to compare one by one which items report as parents those in your parent list, get their real paths, and include that in the filter.

While trying to figure out how to do this via Evaluator, I ran into one of the weirdest bugs/glitches I've seen in Opus.

It seems to be related to using an Evaluator-style filter inside an Evaluator-type function (eg. Select FILTERDEF =some evaluator code here), but I'm not 100% sure. @Jon, @Leo take a look when you get a chance.

Basically, if you use the code below in a regular filesystem path, and the selected item is inside an expanded folder, you somehow end up "navigating" into that folder without actually leaving the original one. This makes you lose the usual left indent (everything shows at the same level). If you refresh, you suddenly land inside the folder where the selected item was. Hard to explain, you need to see it in a video.

Worth mentioning: this doesn't seem to happen in Libraries.

ev bug.dcf (3.6 KB)

@David this code should avoid the bug and also work in (almost) all cases. If not, I'd suggest switching to JScript. IMO it's way easier there.

items = GetItems("s");
n_items = len(items);
if (n_items == 0) return;
is_lib = PathType(source) == "lib";
Output("is lib:" + is_lib);
parents = ArrayCreate();
for (i = 0; i < n_items; i++) {
	item = ArrayGet(items, i);
	parent = Parent(item);
	if (parent == source) continue;
	if (is_lib) {
		Output("Resolving to true path..");
		parent = DisplayName(parent, "r");
	}
	Output("item :" + item + "; parent :" + parent);
	ArrayPush(parents, parent);
}
n_items = len(parents);
if (n_items == 0) return;
last = "";
ArraySort(parents);
unique_parents = ArrayCreate();
for (i = 0; i < n_items; i++) {
	item = ArrayGet(parents, i);
	if (Right(item, 1) == "\\") item = Left(item, Len(item) - 1);
	if (last == item) {
		Output(item + " is dupe");
		continue;
	}
	ArrayPush(unique_parents, EscapeWild(is_lib ? item : EscapeWild(item,"b")));
	last = item;
}
prt = len(unique_parents) > 1;
cmd = is_lib ? "Select FILTERDEF =Match(fullpath,""" :  "Select FILTERDEF fullpath match """;
if (prt) cmd += "(";
cmd += Implode(unique_parents, "|");
if (prt) cmd += ")";
cmd += is_lib ? "\"", ""ph"")" : "\\*""";
Output(cmd);
Run(cmd);
if (!is_lib) return;
items = GetItems("s");
n_items = len(items);
if (n_items == 0) return;
parents = ArrayCreate();
for (i = 0; i < n_items; i++) {
	item = ArrayGet(items, i);
	if (!IsDir(item)) continue;
	ArrayPush(parents, DisplayName(item, "r"));
}
n_items = len(parents);
if (n_items == 0) return;
prt = n_items > 1;
cmd = "Select FILTERDEF =Match(fullpath,""";
if (prt) cmd += "(";
cmd += Implode(parents, "|");
if (prt) cmd += ")";
cmd += "\"", ""ph "")";
Output(cmd);
Run(cmd);

Thanks! Fixed in the next beta.

@Jon
Thankyou ! That worked. :upside_down_face:

@errante
Using Match()is a good idea, but we now have duplication caused by selecting and then resampling the selected items.
I tried to merge the two items = GetItems("s"); -> Run(cmd); .
This seems to work as well or did I miss the point here ?

items = GetItems("s");
n_items = len(items);
if (n_items == 0) return;
is_lib = PathType(source) == "lib";
Output("is lib:" + is_lib);
parents = ArrayCreate();
for (i = 0; i < n_items; i++) 
    {
	item = ArrayGet(items, i);
	parent = Parent(item);
	if (parent == source) continue;
	if (is_lib)
         {         
         Output("Resolving to true path..");
         parent = DisplayName(parent, "r");
         IsDir(item) ?  ArrayPush(parents, DisplayName(item, "r")) :  ArrayPush(parents, parent);
         }                  
	Output("item :" + item + "; parent :" + parent);
    ArrayPush(parents, parent);
    }
n_items = len(parents);
if (n_items == 0) return;
last = "";
ArraySort(parents);
unique_parents = ArrayCreate();
for (i = 0; i < n_items; i++) 
    {
	item = ArrayGet(parents, i);
	if (Right(item, 1) == "\\") item = Left(item, Len(item) - 1);
	if (last == item) 
        {
		Output(item + " is dupe");
		continue;
       	}
    ArrayPush(unique_parents, EscapeWild(item));
    last = item;
    }
prt = len(unique_parents) > 1;
cmd = "Select FILTERDEF =Match(fullpath,""";
if (prt) cmd += "(";
cmd += Implode(unique_parents, "|");
if (prt) cmd += ")";
cmd += "\"", ""ph"")";
Output(cmd);
Run(cmd);

There shouldn't be duplicates between the two processes, and even if there were, it wouldn't really matter.

We run the second process after changing the selection in the first one (FWIW this wouldnt even be needed with scripting, so take it as the payment for using Evaluator here), so we can also grab all its children (which won't have lib:\\ at all in their fullpath, including its parents).

Your code above wouldn’t work if, in an expanded library, you have two folders expanded more than one level deep and you only select one. All the children from the second one wouldn't get selected, and I'm guessing that's what you meant by "select files/folders and below".

It could probably be done in a single loop, but that would mean processing all items (not just the selected ones), which would make it slower in cases where it's not needed (eg. non-library paths).

I'll take your advise about learning more scripting.
It would be foolhardy to ignore you on that point.

Sorry to disagree, but after looking at this some more, I don't think this second process is needed at all. We have already resolved to the true paths.

Here are some test screen shots with script logs.
The status column flames mark the initial selections.
The code used is posted below the screenshots.

items = GetItems("s");
n_items = len(items);
if (n_items == 0) return;
is_lib = PathType(source) == "lib";
Output("is lib:" + is_lib);
parents = ArrayCreate();
for (i = 0; i < n_items; i++) 
    {
	item = ArrayGet(items, i);
	parent = Parent(item);
	if (parent == source) continue;
	if (is_lib)
         {         
         Output("Resolving to true path..");
         parent = DisplayName(parent, "r");         
         }                  
	Output("item :" + item + "; parent :" + parent);
    ArrayPush(parents, parent);
    }
n_items = len(parents);
if (n_items == 0) return;
last = "";
ArraySort(parents);
unique_parents = ArrayCreate();
for (i = 0; i < n_items; i++) 
    {
	item = ArrayGet(parents, i);
	if (Right(item, 1) == "\\") item = Left(item, Len(item) - 1);
	if (last == item) 
        {
		Output(item + " is dupe");
		continue;
       	}
    ArrayPush(unique_parents, EscapeWild(item));
    last = item;
    }
prt = len(unique_parents) > 1;
cmd = "Select FILTERDEF =Match(fullpath,""";
if (prt) cmd += "(";
cmd += Implode(unique_parents, "|");
if (prt) cmd += ")";
cmd += "\"", ""ph"")";
Output(cmd);
Run(cmd);

Well, this is what happens if I use your code (the one from above, not sure if it's the same as the one in your previous post).

You can see that the children of those folders that aren't directly related to the lib:\\Documents folder, but still appear nested in the file display relative to it (so technically they can be considered its children in this context), are not getting selected. I assume that's not what you want.

That's why we need to use the folders from the initial selection so we can select all their children, that's what the second part of my code does. And that's why it's limited to libraries only, because it shouldn't be needed anywhere else.

From what I can tell, the following code gives the same results as your code:

Select FILTERDEF location match "{filepath|escbackslash|noterm|..}" nowild

So it seems we have a semantics problem.
When you select AutoHotKey under documents and then press the button, I would want only files/folders under documents selected. The wanted new selections are local to documents.

Your videos look fine to me. I don't understand what was not properly selected .

Tomorrow is another day ! :grin:

Select all files/folders in expanded folders that have selected files/folders and below:
In my example, I understand that since I have the AutoHotkey folder selected, which is inside the expanded lib:\\Documents folder, then ALL items that are also shown nested within that folder should be selected. Your code doesn't select the abc folder or its contents, even though they are displayed inside lib:\\Documents.
I don't see another way to interpret your request that would make your code make sense, sorry.
Because it would feel like a pretty random selection to me, selecting the file untitled.ahk but not the abc folder, even though both are clearly nested at the same level.

I see it now in your first video.
When I first looked at the videos I thought they were both the same. I downloaded them and they had the same file name.

I had not seen that happen at all here.

@jinsight
Yes, that's a very good point.
In fact with normal file system paths, this seems to do the same thing but doesn't work in Libraries .

@nodeselect
Select FILTERDEF location match "{filepath|escbackslash|noterm|..}*"

But I'm keeping my Evaluator variant as I may find it useful for something else.