One file display hangs (in dual mode) after delete tab from script

Hi,

Note : Dumps sent to crashdumps@gpsoft.com.au under same title as this thread.

trying to work on a FAYT script to load/unload dynamically tab groups.
When trying to close a tab from the script, I did :

var cmd = DOpus.Create.Command();
for (var e= new Enumerator(this.GrpTabs(name)); !e.atEnd(); e.moveNext()) {
	dout("Trying to close tab '" + e.item().displayed_label + "'");
	//cmd.Clear();
	cmd.SetSourceTab(e.item());
	cmd.RunCommand("GO TABCLOSE");
}

this.GrpTabs(name) : Is a vector of Tab objects. Only one tab in the execution context here.

Three things happened :
First
The tab that got closed was not the proper one.
It seems it closed the last tab of the "left" file display (maybe it was the one with focus, I can't remember).

Second
My lister went in a strange status : The tab that should have been closed was reported by the script with a displayed_label as undefined (trying to call again the close command of the script builds a list of suggestions were there is a log showing that). The subsequent call to tab.vars yields an error about vars being Null or not an object.

Third
I tried to close manually that tab to try another way of closing my tabs from the script : the upper file display became totally irresponsive after giving focus to the tab. Note that the upper file display did not update (content displayed stayed on the previously displayed tab).

In case you want to replicate, I join the script.
It's mainly inspired by the first script @Leo provided as example of FAYT scripts.
As of now, it has 2 commands (open and close), is binded to "$", and :

  • open lists groups that are in the settings, choosing one opens new tabs, rename them, and sets tabs variables to be able to recognize them.
  • close displays a list of groups that were formerly opened by the previous command to be able to close those tabs.

FAYT TabGroupsTest.opusscriptinstall (7.1 KB)
And code to ease inspection (sorry, still under construction, so not really clean and easy to read, lots of comments) :

// Adds a FAYT Quick Key which contains 4 commands :
// * open: 		lists all the TabGroups declared within Opus so you can decide to add one.
//				Note that the group will be opened without closing other tabs, even if it'the group settings.
//
// * close: 	lists for the current lister, all groups that have been opened prevously with the add command
//				that still have tabs opeened in at least one of the pane.
//				Executing the command will close these tabs.
//				/!\ Tabs are deleted regardless of the current folder they are 
//
// * rebase Group IDs: 	Each time a group is added, it is assigned an ID (starting at 1 for frst group, increment by 1).
//						Even when you remove a group, IDs continue to grow (in order not to create confusion between a recently
//						closed group and a newly created one). Since IDs are persistent, it can be useful to reset IDs to the
//						lowest possible number (e.g. the highest ID among groups that still have tabs in the lister).
//
// * make permanent: 	lists all current inserted groups. Executing the command will remove markers for that group so it can no longer be removed
//						by this script. It's made permanent (unles you decide to close the tabs by yourself).
//
// * forget All : 	Similar to making everything permanent. It will also reinitialize the group IDs to 1.
//
// * help : Will open a popup with the help for the different commands.
// ---

// Adds a FAYT Quick Key which lists the folders directly below a specified path, using custom rules to generate
// shorthand versions of them. Intended for quick navigation regardless of the current folder.



// TODO: Configuration UI. Including making the command name etc. configurable. Multiple commands/folder sets from one script?
// TODO: A way to refresh, since it caches the folder on initial use.


// (?:^| |\(|\[|\/|\\)([a-zA-ZäöüßÄÖÜ])


// Global cache variables. Do not change.
var g_OpenSugs 			= null;
var g_RemoveSugs 		= null;
var g_MakePermanent 	= null;
// var g_paths = null;		// Really used
// var g_tabNamePattern = "⬛%id▶ %N";		// Replaced by script configuration


function OnInit(initData) {
	initData.name = "FAYT Dynamic TabGroups";
	initData.version = "1.0";
	initData.copyright = "(c) 2024 Stéphane Alessio";
	initData.desc = "";
	initData.default_enable = true;
	initData.min_version = "13.10";
	initData.group = "FAYT Scripts";

	// settings & defaults
	initData.config_desc = DOpus.Create.Map();
	var option_name = "";

	option_name = "Tabs naming scheme";
	initData.Config[option_name] = "⬛%id▶ %N";
	initData.config_desc(option_name) = "Use '%id' where you want the group ID to be inserted. Other options (%N, %R, %P, %!) are described in Opus manual (GO TABNAME)";
	// use with Script.Config["Tabs naming scheme"]

	option_name = "Suggestions retention delay";
	initData.Config[option_name] = 4000;
	initData.config_desc(option_name) = "Represents the time in ms between two requests for suggestions under which the suggestion list is not rebuilt";
}

dout("************************ Building actions list");
FAYTcommandsList = DOpus.Create.Map();

// OPEN
// =====
openCommand = new FAYTcommand("open");
openCommand.suggestionRetentionTime = 4000;
openCommand.BuildSuggestions = BuildOpenGroupsSuggestions;
openCommand.ExecuteAction = ExecuteOpenAction;
openCommand.GrpPaths = null;
FAYTcommandsList("open") = openCommand;

// CLOSE
// ======
closeCommand = new FAYTcommand("close");
closeCommand.suggestionRetentionTime = 4000;
closeCommand.BuildSuggestions = BuildCloseGroupsSuggestions;
closeCommand.ExecuteAction = ExecuteCloseAction;
closeCommand.GrpTabs = null;
FAYTcommandsList("close") = closeCommand;

// function FAYTcommand(verb) {
// 	this.verb = verb;
// 	this.lastSuggestionBuildTime = 0;
// 	this.suggestionRetentionTime = 0;
// 	this.latestSuggestions = null;
// 	this.BuildSuggestions = function(scriptFAYTData) { this.latestSuggestions = null; return this.latestSuggestions; };
// 	this.ExecuteAction = function(scriptFAYTData) { return null; }
// }



function OnAddCommands(addCmdData)
{
	var cmd = addCmdData.AddCommand();
	cmd.name = "FAYTDynGroups";
	cmd.method = "OnFAYTDynGroups";
	cmd.desc = "";
	cmd.label = "FAYTDynGroups";
	cmd.template = "";
	cmd.hide = true; // Hide from button editor menus
	cmd.icon = "script";

	var fayt = cmd.fayt;
	fayt.enable = true;
	fayt.key = "$";
	fayt.textcolor = "##ff00ff";
	fayt.backcolor = "#000000";
	fayt.label = "Dynamic Groups";
//	fayt.flags - optional Map of flags (flag value -> label) - need to use DOpus.Create.Map
	fayt.realtime = true; // Call on each keypress, not just after return
	fayt.wantempty = false;

	var cmd = addCmdData.AddCommand();
	cmd.name = "FAYTDynGroups_ClearAll";
	cmd.method = "OnFAYTDynGroups_ClearAll";
	cmd.desc = "";
	cmd.label = "FAYTDynGroups_ClearAll";
	cmd.template = "";
	cmd.hide = false; // Hide from button editor menus
	cmd.icon = "script";
}

function OnFAYTDynGroups(scriptFAYTData)
{
	dout("***IN*** '" + scriptFAYTData.cmdline + "' Flags=" + scriptFAYTData.flags + " Key=" + scriptFAYTData.key  + "(" + scriptFAYTData.key.length + ")" + " suggest=" + scriptFAYTData.suggest);

	var localSuggestions = DOpus.Create.Map();
	// g_paths = null;
	// dout("fayt = "+ scriptFAYTData.fayt);
	if (scriptFAYTData.fayt != "FAYTDynGroups")
	{
		DOpus.Output('Unexpected FAYT: "' + scriptFAYTData.fayt + '"');
		scriptFAYTData.tab.CloseFAYT();
		return;
	}

	// Set suggestions retention delay for all actions
	SetRetentionDelayOnActions();

	// Build suggestion list from possible actions (all matching actions feed the suggestions list)
	for (var e = new Enumerator(FAYTcommandsList); !e.atEnd(); e.moveNext()) {
		if (FAYTcommandsList(e.item()).MatchesInput(scriptFAYTData.cmdline))
			localSuggestions.merge(FAYTcommandsList(e.item()).BuildSuggestions(scriptFAYTData));
	}

	// var action = FAYTcommandsList("open");
	// Checking command and building suggestions
	// Suggestion have to be build as soon as the user enters a letter.
	// Upon entering more letters, there's no need to rebuild, unless we're back to one letter.

	// if (scriptFAYTData.cmdline.toLowerCase() == "o") {
	// 	g_OpenSugs = null;
	// 	dout("Building group suggestions");
	// 	BuildOpenGroupsSuggestions();
	// 	if (g_OpenSugs == null) return;	// No tab group found
	// }
	// if (scriptFAYTData.cmdline.match(/o/i))
	// 	localSuggestions = g_OpenSugs;

	// localSuggestions = action.BuildSuggestions(scriptFAYTData);
	// g_OpenSugs = localSuggestions;


	var tab = scriptFAYTData.tab;

	if (scriptFAYTData.key == "return")
	{
		// Execute the action upon each FAYTcommand that provided this suggestion (should be only one)
		for (var e = new Enumerator(FAYTcommandsList); !e.atEnd(); e.moveNext())
			if (FAYTcommandsList(e.item()).latestSuggestions.exists(scriptFAYTData.cmdline))
				FAYTcommandsList(e.item()).ExecuteAction(scriptFAYTData);
		// action.ExecuteAction(scriptFAYTData);
		// ExecuteSuggestionInTab(tab, scriptFAYTData.cmdline);
		return;
	}

	if (scriptFAYTData.suggest)
	{
		dout("Suggesting " + ((localSuggestions!=null)?localSuggestions.count:0) + " results");
		tab.UpdateFAYTSuggestions(localSuggestions);
	}
}

function SetRetentionDelayOnActions() {
	for (var e = new Enumerator(FAYTcommandsList); !e.atEnd(); e.moveNext())
		FAYTcommandsList(e.item()).suggestionRetentionTime = Script.Config["Suggestions retention delay"];
}

function ExecuteOpenAction(scriptFAYTData) {
	var name = scriptFAYTData.cmdline;
	var tab = scriptFAYTData.tab;

	if (this.latestSuggestions.exists(scriptFAYTData.cmdline))
		// dout("[" + this.verb + "] Execute on : '" + name + "' '>> Group Name : '" + this.latestSuggestions(name) + "'");
		dout("[" + this.verb + "] Execute on : '" + name + "' '>> Group Name : '" + this.GrpPaths(name) + "'");
	else
		dout("Trying to execute on unknown group");

	if (!name.match(/^open /i)) {
		dout("Unknown action : " + name);
		return;
	}

	tab.CloseFAYT();
	dout("Request to open group '" + this.GrpPaths(name) + "'");

	var cmd = DOpus.Create.Command();
	cmd.AddLine('GO TABGROUPLOAD "' + this.GrpPaths(name) + '" TABCLOSEALL=no');
	var r = cmd.Run();
	// dout("Execution status = " + r);

	// dout("Cmd.results.newtabs");
	var openedTabs = DOpus.Create.Vector();
	var seqId = GetNextSequenceId(tab.lister);
	var newTabName = Script.Config["Tabs naming scheme"].replace(/%id/, seqId);
	dout("New tab name : " + newTabName);

	cmd.Clear();

	for (var e = new Enumerator(cmd.results.newtabs); !e.atEnd(); e.moveNext()) {
		var newTab = e.item();
		dout("New Tab => " + newTab.displayed_label + " >> " + newTab.path);
		newTab.vars.Set("DynGrpId", seqId);
		newTab.vars("DynGrpId").persist = true;
		newTab.vars.Set("DynGrpName", this.GrpPaths(name));
		newTab.vars("DynGrpName").persist = true;
		openedTabs.push_back(newTab);
	}

	tab.lister.Update();
	var activeTab = tab.lister.activetab;
	dout("== Active tab is on the " + ((activeTab.right)?"Right/Bottom pane":"Left/Up pane"));
	var currentPane = (activeTab.right)?"right":"left";

	for (var e = new Enumerator(openedTabs); !e.atEnd(); e.moveNext()) {
		var newTab = e.item();
		dout("Tab " + newTab.displayed_label + " is on " + ((newTab.right)?"Right/Bottom pane":"Left/Up pane"));
		cmd.Clear();

		cmd.SetSourceTab(newTab);
		cmd.AddLine('GO TABNAME="' + newTabName + '"');
		var res = cmd.Run();
		dout("Command status (>0 expected) = " + res + " | " + cmd.results.result);
	}

	dout("End of open execute action.");
	return;
}

function ExecuteCloseAction(scriptFAYTData) {
	var name = scriptFAYTData.cmdline;
	var tab = scriptFAYTData.tab;

	if (this.latestSuggestions.exists(scriptFAYTData.cmdline))
		// dout("[" + this.verb + "] Execute on : '" + name + "' '>> Group Name : '" + this.latestSuggestions(name) + "'");
		dout("[" + this.verb + "] Execute on : '" + name + "' '>> # tabs = '" + this.GrpTabs(name).count + "'");
	else
		dout("Trying to execute on unknown group");

	if (!name.match(/^close /i)) {
		dout("Unknown action : " + name);
		return;
	}

	tab.CloseFAYT();
	dout("Request to close group with " + this.GrpTabs(name).count + " tabs.");

	var cmd = DOpus.Create.Command();
	for (var e= new Enumerator(this.GrpTabs(name)); !e.atEnd(); e.moveNext()) {
		dout("Trying to close tab '" + e.item().displayed_label + "'");
		//cmd.Clear();
		cmd.SetSourceTab(e.item());
		cmd.RunCommand("GO TABCLOSE");

	}

	// --- TMP
	return;


	cmd.AddLine('GO TABGROUPLOAD "' + this.GrpPaths(name) + '" TABCLOSEALL=no');
	var r = cmd.Run();
	// dout("Execution status = " + r);

	// dout("Cmd.results.newtabs");
	var openedTabs = DOpus.Create.Vector();
	var seqId = GetNextSequenceId(tab.lister);
	var newTabName = Script.Config["Tabs naming scheme"].replace(/%id/, seqId);
	dout("New tab name : " + newTabName);

	cmd.Clear();

	for (var e = new Enumerator(cmd.results.newtabs); !e.atEnd(); e.moveNext()) {
		var newTab = e.item();
		dout("New Tab => " + newTab.displayed_label + " >> " + newTab.path);
		newTab.vars.Set("DynGrpId", seqId);
		newTab.vars("DynGrpId").persist = true;
		newTab.vars.Set("DynGrpName", this.GrpPaths(name));
		newTab.vars("DynGrpName").persist = true;
		openedTabs.push_back(newTab);
	}

	tab.lister.Update();
	var activeTab = tab.lister.activetab;
	dout("== Active tab is on the " + ((activeTab.right)?"Right/Bottom pane":"Left/Up pane"));
	var currentPane = (activeTab.right)?"right":"left";

	for (var e = new Enumerator(openedTabs); !e.atEnd(); e.moveNext()) {
		var newTab = e.item();
		dout("Tab " + newTab.displayed_label + " is on " + ((newTab.right)?"Right/Bottom pane":"Left/Up pane"));
		cmd.Clear();

		cmd.SetSourceTab(newTab);
		cmd.AddLine('GO TABNAME="' + newTabName + '"');
		var res = cmd.Run();
		dout("Command status (>0 expected) = " + res + " | " + cmd.results.result);
	}

	dout("End of open execute action.");
	return;
}

function ExistsInCollection(item, collection) {
	for (var e = new Enumerator(collection); !e.atEnd(); e.moveNext())
		if (item === e.item()) return true;
	return false;
}

function BuildOpenGroupsSuggestions(scriptFAYTData) {
	var curDate = new Date();
	var rebuildTimeMsg = new Array();
	rebuildTimeMsg.push("[OPEN menu] ");
	rebuildTimeMsg.push(((curDate.valueOf() - this.lastSuggestionBuildTime.valueOf()) < this.suggestionRetentionTime)?"No rebuild":"REBUILD");
	rebuildTimeMsg.push("diff=" + (curDate.valueOf() - this.lastSuggestionBuildTime.valueOf()));
	rebuildTimeMsg.push("ref=" + this.suggestionRetentionTime);
	rebuildTimeMsg.push("curDate=" + curDate.valueOf());
	rebuildTimeMsg.push("lastDate=" + this.lastSuggestionBuildTime.valueOf());
	dout(rebuildTimeMsg.join(" | "));

	// dout("curDate=" + curDate.valueOf());
	// dout("lastDate=" + this.lastSuggestionBuildTime.valueOf());
	// dout("diff=" + (curDate.valueOf() - this.lastSuggestionBuildTime.valueOf()));
	// dout("ref=" + this.suggestionRetentionTime );
	// dout("curDate=" + curDate.valueOf() + " | lastDate=" + this.lastSuggestionBuildTime.valueOf() + " | diff=" + (curDate.valueOf() - this.lastSuggestionBuildTime.valueOf()) + " | ref=" + this.suggestionRetentionTime );
	if ( (curDate.valueOf() - this.lastSuggestionBuildTime.valueOf()) < this.suggestionRetentionTime) {
		this.lastSuggestionBuildTime = new Date();
		return this.latestSuggestions;
	}

	this.latestSuggestions = DOpus.Create.Map();
	this.GrpPaths = DOpus.Create.Map();

	// Parse TabGroups
	// Analyzing tabgroups
	var allTabGroups = GetGroupsFromSettings(DOpus.TabGroups);
	dout("Total # of tabGroups : " + allTabGroups.count);

	var maxGroupLength = GetMaxKeysLength(allTabGroups);
	dout("Max grp length = " + maxGroupLength);
	for (var e = new Enumerator(allTabGroups); !e.atEnd(); e.moveNext()) {
		var grpPath = e.item();
		dout("grpPath = " + grpPath);
		var grp = allTabGroups(grpPath);
		var hint = grpPath;
		dout("padding after with " + (maxGroupLength + 6 - grpPath.length) + " spaces");
		hint += " ".repeat(maxGroupLength + 3 - grpPath.length);

		if (grp.dual) {
			// Manage extra desc for dual listers

			hint += "▬▬  ◤ " + grp.lefttabs.count + " tab" + ((grp.lefttabs.count>1)?"s":"");
			hint += " | ◢ " + grp.righttabs.count + " tab" + ((grp.righttabs.count>1)?"s":"");
		}
		else {
			hint += "▬  " + grp.tabs.count + " tab" + ((grp.tabs.count>1)?"s":"");
		}

		// this.latestSuggestions("open " + GetExtendedInitials(initials, this.latestSuggestions)) = e.item();
		var initials = GetInitials(grpPath);
		var grpCommand = "open " + GetExtendedInitials(initials, this.latestSuggestions);
		this.latestSuggestions(grpCommand) = hint;
		this.GrpPaths(grpCommand) = grpPath;
	}

	this.lastSuggestionBuildTime = new Date();
	return this.latestSuggestions;
}

function BuildCloseGroupsSuggestions(scriptFAYTData) {
	var curDate = new Date();
	var rebuildTimeMsg = new Array();
	rebuildTimeMsg.push("[CLOSE menu] ");
	rebuildTimeMsg.push(((curDate.valueOf() - this.lastSuggestionBuildTime.valueOf()) < this.suggestionRetentionTime)?"No rebuild":"REBUILD");
	rebuildTimeMsg.push("diff=" + (curDate.valueOf() - this.lastSuggestionBuildTime.valueOf()));
	rebuildTimeMsg.push("ref=" + this.suggestionRetentionTime);
	rebuildTimeMsg.push("curDate=" + curDate.valueOf());
	rebuildTimeMsg.push("lastDate=" + this.lastSuggestionBuildTime.valueOf());
	dout(rebuildTimeMsg.join(" | "));

	if ( (curDate.valueOf() - this.lastSuggestionBuildTime.valueOf()) < this.suggestionRetentionTime) {
		this.lastSuggestionBuildTime = new Date();
		return this.latestSuggestions;
	}

	this.latestSuggestions = DOpus.Create.Map();
	this.GrpTabs = DOpus.Create.Map();

	// Parse tabs from lister to identify tabs associated to a group
	// Check if dual lister
	// If dual lister : 
	// 		get orientation (??? or we use the corners like for open ??)
	//  	Store tabs with location
	//		Make an objecttabs, lefttabs, righttabs ??

	// Find all tabs marked as "in a dynamic group" both in leftabs and in righttabs
	var allTabs = DOpus.Create.Map();
	scriptFAYTData.tab.lister.Update();	// refresh with new tabs, deleted tabs, etc ...
	for (var e = new Enumerator(scriptFAYTData.tab.lister.tabsleft); !e.atEnd(); e.moveNext()) {
		var tab = e.item();
		dout("working on tab '" + tab.displayed_label + "'");
		if (tab.vars.exists("DynGrpId")) {
			if (tab.vars.exists("DynGrpName")) {
				var key = tab.vars("DynGrpId") + "_" + tab.vars("DynGrpName");
				if (!allTabs.exists(key)) {
					allTabs(key) = DOpus.Create.Map();
					allTabs(key)("left") = {"id": tab.vars("DynGrpId"), "name": tab.vars("DynGrpName"), "list": DOpus.Create.Vector() };
				}
				allTabs(key)("left").list.push_back(tab);
			}
			else {
				dout("ERROR : tab has a group Id (" + tab.vars("DynGrpId") + ") but no group Name");
				var key = tab.vars("DynGrpId") + "_[Uknown]";
				if (!allTabs.exists(key)) {
					allTabs(key) = DOpus.Create.Map();
					allTabs(key)("left") = {"id": tab.vars("DynGrpId"), "name": "[Unknown]", "list": DOpus.Create.Vector() };
				}
				allTabs(key)("left").list.push_back(tab);
			}
		}
	}
	for (var e = new Enumerator(scriptFAYTData.tab.lister.tabsright); !e.atEnd(); e.moveNext()) {
		var tab = e.item();
		if (tab.vars.exists("DynGrpId")) {
			if (tab.vars.exists("DynGrpName")) {
				var key = tab.vars("DynGrpId") + "_" + tab.vars("DynGrpName");
				if (!allTabs.exists(key))
					allTabs(key) = DOpus.Create.Map();
				if (!allTabs(key).exists("right"))
					allTabs(key)("right") = { "id": tab.vars("DynGrpId"), "name": tab.vars("DynGrpName"), "list": DOpus.Create.Vector() };
				allTabs(key)("right").list.push_back(tab);
			}
			else {
				dout("ERROR : tab has a group Id (" + tab.vars("DynGrpId") + ") but no group Name");
				var key = tab.vars("DynGrpId") + "_[Uknown]";
				if (!allTabs.exists(key))
					allTabs(key) = DOpus.Create.Map();
				if (!allTabs(key).exists("right"))
					allTabs(key)("right") = { "id": tab.vars("DynGrpId"), "name": "[Unknown]", "list": DOpus.Create.Vector() };
				allTabs(key)("right").list.push_back(tab);
			}
		}
	}

	// Enumerate all keys to build suggestions & GrpTabs object.
	for (var e = new Enumerator(allTabs); !e.atEnd(); e.moveNext()) {
		var key = e.item();
		var tabs = allTabs(e.item());

		var grpCommand = "";

		if (tabs.exists("left")) {
			var id = tabs("left").id;
			var grpName = tabs("left").name;

			var hint = "[Grp:" + id + "] " + grpName + "    ";
			if (scriptFAYTData.tab.lister.dual == 0)		// Single Display
				hint += "[ ▬  " + tabs("left").list.count + " tab" + ((tabs("left").list.count>1)?"s ]":" ]");
			else if (scriptFAYTData.tab.lister.dual == 1)	// Dual Display - Vertical Layout
				hint += "[ ◀ " + tabs("left").list.count + " tab" + ((tabs("left").list.count>1)?"s ":" ");		//  ▶
			else if (scriptFAYTData.tab.lister.dual == 2)	// Dual Display - Horizontal Layout
				hint += "[ ▲ " + tabs("left").list.count + " tab" + ((tabs("left").list.count>1)?"s ":" ");		//  ▼

			var initials = GetInitials(tabs("left").id + " - " + tabs("left").name);
			grpCommand = "close " + GetExtendedInitials(initials, this.latestSuggestions);
			this.latestSuggestions(grpCommand) = hint;
			this.GrpTabs(grpCommand) = tabs("left").list;
		}

		if (tabs.exists("right")) {
			var id = tabs("right").id;
			var grpName = tabs("right").name;

			var hint = "";
			var newTabs = null;
			if (grpCommand == "") {	// There were no 'left/up' tabs for this group
				var initials = GetInitials(tabs("right").id + " - " + tabs("right").name);
				grpCommand = "close " + GetExtendedInitials(initials, this.latestSuggestions);
				hint = "[Grp:" + id + "] " + grpName + "    [ ";
				oldTabs = DOpus.Create.Vector();
			}
			else {
				hint = this.latestSuggestions(grpCommand) + " | ";
				oldTabs = this.GrpTabs(grpCommand);
			}

			if (scriptFAYTData.tab.lister.dual == 1)	// Dual Display - Vertical Layout
				hint += "▶ " + tabs("right").list.count + " tab" + ((tabs("right").list.count>1)?"s ":" ");		//  ▶
			else if (scriptFAYTData.tab.lister.dual == 2)	// Dual Display - Horizontal Layout
				hint += "▼ " + tabs("right").list.count + " tab" + ((tabs("right").list.count>1)?"s ":" ");		//  ▼

			oldTabs.append(tabs("right").list);
			this.GrpTabs(grpCommand) = oldTabs;
		}

		if (scriptFAYTData.tab.lister.dual >= 1)
			hint += "]";
		this.latestSuggestions(grpCommand) = hint;
	}

	this.lastSuggestionBuildTime = new Date();
	return this.latestSuggestions;
}

function GetExtendedInitials(initials, sugsList) {
	// dout("GetExtendedInitials for '" + initials + "'");
	if (!sugsList.exists("open " + initials)) return initials;
	// dout("Already exists");
	var i=1;
	while (sugsList.exists("open " + initials + "_" + i)) i++;
	// dout("Requires renaming to '" + initials + "_" + i + "'");
	return initials + "_" + i;
}

function GetGroupsFromSettings(tabGroupList, path) {
	var rootPath = path || "";
	// dout("Path = '" + rootPath + "' | Input tabGroupList #" + tabGroupList.count + " items");
	var m = DOpus.Create.Map();
	for (var e = new Enumerator(tabGroupList); !e.atEnd(); e.moveNext()) {
		var grp = e.item();
		// dout("GetGroupsFromSettings : Processing : " + grp.name);
		if (grp.folder)
			m.merge(GetGroupsFromSettings(grp, rootPath + grp.name + "/"));
		else
			m(rootPath + grp.name) = grp;
	}
	// dout("Ending GetGroupsFromSettings for path=" + rootPath);
	return m;
}

function GetInitials(s) {
	// dout("GetInitials: >" + s + "<");
	var reFolders = new RegExp("/([a-zA-Z])(?=.*?/)","g");
	var regexp = /(?:^| |\(|\[|\/|\\|-)([0-9a-zA-ZäöüßÄÖÜ])/g;

	var out = "";
	// First Letter of each folder in path
	var foldersFirstLetters = ("/"+s).match(reFolders);
	if (foldersFirstLetters) {
		dout("folder match");
		for (var i = 0; i < foldersFirstLetters.length; i++) {
			// dout("adding:" + foldersFirstLetters[i]);
			out += foldersFirstLetters[i].replace('/', '');
		}
	}
	// dout("Folders 'id' = " + out);

	// Then get first letter of each word
	var r = GetLastPathPart(s).match(regexp);
	for (var i = 0; i < r.length; i++)
		out += r[i].replace(/ |\(|\[|\\/, '');

	// dout ("output=" + out.toUpperCase());
	return out.toUpperCase();
}

function GetNextSequenceId(lister) {
	var seqVar;
	if (lister.vars.exists("DynGrpMaxSeq")) {
		dout("Sequence exists");
		seqVar = lister.vars("DynGrpMaxSeq");
		dout("Value = " + seqVar.value);

	}
	else {
		lister.vars.Set("DynGrpMaxSeq", 0);
		dout("exists after adding ? = " + lister.vars.exists("DynGrpMaxSeq"));
		seqVar = lister.vars("DynGrpMaxSeq");
		seqVar.persist = true;
	}

	seqVar.value = seqVar.value + 1;
	dout("Lister seq id returned = " + seqVar.value);
	return seqVar.value;
}

// Really used ??
function TrimSpace(s)
{
  return s.replace(/^\s+|\s+$/gm,'');
}


// function GetLastPathPart(s)
// {
// 	s = s + "";
// 	while (s.length > 0 && s.slice(-1) == "\\")
// 	{
// 		s = s.slice(0,-1);
// 	}
// 	var slashPos = s.lastIndexOf("\\");
// 	if (slashPos != -1)
// 	{
// 		s = s.slice(slashPos+1);
// 	}
// 	return s;
// }

function GetLastPathPart(s)
{
	s = s + "";
	while (s.length > 0 && s.slice(-1) == "/")
	{
		s = s.slice(0,-1);
	}
	var slashPos = s.lastIndexOf("/");
	if (slashPos != -1)
	{
		s = s.slice(slashPos+1);
	}
	return s;
}

function GetMaxKeysLength(map) {
	var maxLength = 0;
	for (var e = new Enumerator(map); !e.atEnd(); e.moveNext())
		maxLength = Math.max(maxLength, e.item().length);

	return maxLength;
}

///////////////////////////////////////////////////////////////////////////
if (!String.prototype.padStart) {
	String.prototype.padStart = function padStart(targetLength,padString) {
        targetLength = targetLength>>0; //truncate if number or convert non-number to 0;
        // dout("in padstart / targetLength=" + targetLength);
        padString = String((typeof padString !== 'undefined' ? padString : ' '));
        if (this.length > targetLength) {
            return String(this);
        }
        else {
            targetLength = targetLength-this.length;
            if (targetLength > padString.length) {
                padString += padString.repeat(targetLength/padString.length); //append to original to ensure we are longer than needed
            }
            return padString.slice(0,targetLength) + String(this);
        }
    };
}


///////////////////////////////////////////////////////////////////////////
if (!String.prototype.repeat) {
	String.prototype.repeat = function(count) {
	    'use strict';
	    if (this == null)
			throw new TypeError('can\'t convert ' + this + ' to object');

	    var str = '' + this;
	    count = +count;
	    if (count != count)
			count = 0;

	    if (count < 0)
			throw new RangeError('repeat count must be non-negative');

	    if (count == Infinity)
			throw new RangeError('repeat count must be less than infinity');

	    count = Math.floor(count);
	    if (str.length == 0 || count == 0)
			return '';

	    // Ensuring count is a 31-bit integer allows us to heavily optimize the
	    // main part. But anyway, most current (August 2014) browsers can't handle
	    // strings 1 << 28 chars or longer, so:
	    if (str.length * count >= 1 << 28)
			throw new RangeError('repeat count must not overflow maximum string size');

	    var maxCount = str.length * count;
	    count = Math.floor(Math.log(count) / Math.log(2));
	    while (count) {
	       str += str;
	       count--;
	    }
	    str += str.substring(0, maxCount - str.length);
	    return str;
	};
}


///////////////////////////////////////////////////////////////////////////
// Generic FAYTcommand class
function FAYTcommand(verb) {
	this.verb = verb;
	this.lastSuggestionBuildTime = new Date(0);
	this.suggestionRetentionTime = 0;
	this.latestSuggestions = DOpus.Create.Map();
	this.BuildSuggestions = function(scriptFAYTData) { this.latestSuggestions = null; return this.latestSuggestions; };
	this.ExecuteAction = function(scriptFAYTData) { return null; }
	this.MatchesInput = function(s) { return this.verb.match(new RegExp('^' + s.substring(0, this.verb.length), "i")); }
}


///////////////////////////////////////////////////////////////////////////
// Command to clear all variables in Lister and in tabs
// ... everything is forgottent ...
function OnFAYTDynGroups_ClearAll(scriptCmdData) {
	for(var e = new Enumerator(FAYTcommandsList); !e.atEnd(); e.moveNext())
		e.item().latestSuggestions = null;
	// g_OpenSugs = null;
	// g_RemoveSugs = null;
	// g_MakePermanent = null;
	// g_paths = null;		// Unused so far ...

	var lister = scriptCmdData.func.sourcetab.lister;
	if (lister.vars.exists("DynGrpMaxSeq"))  lister.vars.Delete("DynGrpMaxSeq");
	for (var e = new Enumerator(lister.tabs); !e.atEnd(); e.moveNext()) {
		var tab = e.item();
		if (tab.vars.exists("DynGrpId")) tab.vars.Delete("DynGrpId");
		if (tab.vars.exists("DynGrpName")) tab.vars.Delete("DynGrpName");
	}
 }


///////////////////////////////////////////////////////////////////////////
// Helper dout function
function dout(msg, error, time) {
	if (error == undefined) error = false;
	if (time == undefined) time = true;
	DOpus.output(msg, error, time);
}

EDIT:
After re-reading the manual, I changed the following lines :

cmd.SetSourceTab(e.item());
cmd.RunCommand("GO TABCLOSE");

to

cmd.RunCommand("GO TABCLOSE=" + e.item());

But before that, I retried the previous way, and ended up with a thread exception popup.

Looks like you fixed the script in the edit, but I'd still like to fix that crash/hang.

Is there a more minimal way to reproduce it? The script is quite complicated, which makes working out where things are going wrong quite hard.

FWIW, the process snapshots don't show anything unusual, at least in terms of threads getting hung or waiting on each other; the threads are all idle and waiting for input.

Hello Leo.
I appreciate you want to try and find what went wrong (as it could be an issue that would reveal other side effects).
I'm gonna try and make a simpler script that would reproduce what I think is the issue (basically trying to delete a tab that is not the one with focus through cmd.SetSourceTab(tabWithoutFocus) and cmd.RunCommand("GO TABCLOSE")).
Of course, the issue could take its root in some other previous contextual action ...
I'll let you know as soon as I can manage something reproducible.

@Leo : I reproduced the file display going unresponsive with this button.

function OnClick(clickData)
{
	var cmd = clickData.func.command;
	cmd.deselect = false; // Prevent automatic deselection

	cmd.RunCommand("GO /system NEWTAB");
	var tabSystem = cmd.results.newtabs(0);
	DOpus.Output("Tab path = " + tabSystem.displayed_label);

	cmd.RunCommand("GO /profile NEWTAB");
	var tabProfile = cmd.results.newtabs(0);
	DOpus.Output("Tab path = " + tabProfile.displayed_label);

	// profile tab has focus, let's try and close the system tab (the wrong way)
	cmd.SetSourceTab(tabSystem);
	cmd.RunCommand("GO TABCLOSE");
}

If using this in a dual file display lister, I can not interact with the file display that had focus upon clicking the button. The other file display and the rest of the lister is still responding.

If using in a single file display, the only file display in unresponsive.

Let me know if you can reproduce this with this button.
If not, I will be able to make dumps before and after calling and will send them.

EDIT : I didn't mention it, but if you replace the cmd.SetSourceTab(tabSystem); by cmd.SetSourceTab(tabProfile);, it works fine.

1 Like

Many thanks! A fix for that has been made for the next beta.

The fix also makes the "incorrect" method of closing the tab work on the tab specified by SetSourceTab.

1 Like