﻿/* Everything FAYT for Directory Opus
**Everything Search in FAYT mode**
Everything FAYT © 2024 by Christian Arellano García is licensed under CC BY-NC-ND 4.0 
    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 
*/
var script_name = 'Everything FAYT';
var script_version = '1.2.1';
var script_url_code = '46571';
// Called by Directory Opus to initialize the script
function OnInit(initData) {
	initData.name = script_name;
	initData.version = script_version;
	initData.copyright = '(c) 2023 - 2024 Christian Arellano García';
	initData.url = 'https://resource.dopus.com/t/everything-fayt-fayt-script-for-do-13/46571';
	initData.desc = 'Everything Search FAYT Script';
	initData.default_enable = true;
	initData.min_version = '13.5.1';
	initData.config_desc = DOpus.Create.Map();
	initData.config_groups = DOpus.Create.Map();
	AddConfig('log level', DOpus.Create.Vector(2, 'debug', 'standard', 'warning', 'off'), DOpus.strings.Get('debug'), 'Script Log');
	AddConfig('coll_name', 'Everything', DOpus.strings.Get('coll_name'), 'Results Collection');
	AddConfig('delete_onstartup', false, DOpus.strings.Get('delete_onstartup'), 'Results Collection');
	AddConfig('query_as_name', false, DOpus.strings.Get('query_as_name'), 'Results Collection');
	AddConfig('flags_are_global', true, DOpus.strings.Get('flags_are_global'), 'Search Options');
	AddConfig('flags_quick_key', '?', DOpus.strings.Get('flags_quick_key'), 'General');
	AddConfig('quick_config_key', '!', DOpus.strings.Get('quick_config_key'), 'General');
	AddConfig('max_saved_queries', 20, DOpus.strings.Get('max_saved_queries'), 'General');

	function AddConfig(name, value, desc, group) {
		initData.config[name] = value;
		initData.config_desc(name) = desc;
		initData.config_groups(name) = group;
	}
}
// Called when Directory Opus starts up
function OnStartup(startupData) {
	if (Script.config.delete_onstartup) DeleteColl();
}

function OnAddCommands(addCmdData) {
	var cmd = addCmdData.AddCommand();
	cmd.name = script_name;
	cmd.method = 'OnEverythingSearch';
	cmd.desc = 'Everything Search FAYT Script';
	cmd.label = script_name;
	cmd.template = '';
	cmd.hide = true; // Hide from button editor menus
	cmd.icon = 'find';
	var fayt = cmd.fayt;
	fayt.enable = true;
	fayt.key = '$';
	fayt.backcolor = '#9dff9d';
	fayt.textcolor = '#000000';
	fayt.label = script_name;
	fayt.realtime = true; // Call after return, not on every keypresss
	fayt.flags = DOpus.Create.Map();
	fayt.flags[(1 << 0)] = DOpus.strings.Get('flag0'); //results in new dest tab
	fayt.flags[(1 << 1)] = DOpus.strings.Get('flag1'); //use Find Results collection
	fayt.flags[(1 << 2)] = DOpus.strings.Get('flag2'); //Ignore diacritics
	fayt.flags[(1 << 3)] = DOpus.strings.Get('flag3'); //Case sensitive
	fayt.flags[(1 << 4)] = DOpus.strings.Get('flag4'); //Regular expressions
	fayt.flags[(1 << 5)] = DOpus.strings.Get('flag5'); //Whole words
}

var is_default_filter, quickkey;

function OnEverythingSearch(scriptFAYTData) {
	if (Script.config['log level'] < 2) DOpus.ClearOutput();
	if (scriptFAYTData.fayt != script_name) { //user invoke the command unexpectedly
		Log(4, 'Unexpected FAYT: "' + scriptFAYTData.fayt + '"');
		return;
	}

	if (scriptFAYTData.quickkey) quickkey = scriptFAYTData.quickkey;
	var query = scriptFAYTData.cmdline;
	if (is_default_filter === undefined || !quickkey) is_default_filter = CheckDefaultFilter();

	if (!quickkey) {
		Log(4, 'Unexpected FAYT     : quick key can\'t be empty!');
		return;
	}
	Log(2, '=======' + script_name + ' v' + script_version + '=====================================');

	Log(1, 'quickkey            : ' + quickkey);
	Log(1, 'FAYT cmdline        : ' + query);
	Log(1, 'is default filter   : ' + is_default_filter);

	if (is_default_filter && query.charAt(0) == quickkey) {
		var append_key = quickKey;
		query = query.slice(1);
	}
	else var append_key = '';

	if (query === '' || query === quickkey) scriptFAYTData.tab.UpdateFAYTSuggestions(getSuggestions(append_key + query)); //only suggest custom names
	else scriptFAYTData.tab.UpdateFAYTSuggestions(getSuggestions(append_key + (query.charAt(0) === quickkey ? quickkey : '')));
	//no return key was pressed or query is empty
	if (!query || scriptFAYTData.key != 'return') {
		return;
	}
	var ini = new Date().getTime();

	var tab = scriptFAYTData.tab;
	Log(2, 'active tab          : ' + tab.path);

	//=== INTERNAL FLAG DIALOG ==========================================================================
	//flags_quickkey is the quick key configured for invoke internal flag dialog
	var flags_quickkey = Script.config.flags_quick_key;
	//disable internal flag dialog if their quick key and commmand quick key are the same
	if (flags_quickkey == quickkey) {
		Log(3, 'flags_quickkey is the same as FAYT quick key. This option is disabled until you change flags_quickkey to another key');
		flags_quickkey = '';
	}
	//decide flags origin
	var g_flags = scriptFAYTData.flags;
	//user invoke internal flag dialog
	if (query == flags_quickkey) {
		Log(2, 'Flags configuration activated');
		configFlags(tab.lister, g_flags);
		return;
	}

	//=== QUICK SCRIPT CONFIG ==========================================================================
	//quick_config_key is the quick key configured for invoke script configuration window
	var quick_config_key = Script.config.quick_config_key;
	//disable quick config option if their quick key and commmand quick key are the same
	if (quick_config_key == quickkey) {
		Log(3, 'quick_config_key is the same as FAYT quick key. This option is disabled until you change quick_config_key to another key');
		quick_config_key = '';
	}
	var cmd = DOpus.Create.Command();
	cmd.SetSourceTab(tab);
	//user invoke quick script config
	if (query == quick_config_key) {
		Log(2, 'Quick config activated');
		cmd.RunCommand('Prefs SCRIPTS=EverythingFAYT.js*');
		return;
	}

	//=== CHECK EVERYTHING PROCESS ====================================================================
	var util = DOpus.Create.SysInfo();
	if (!util.FindProcess('Everything64.exe') && !util.FindProcess('Everything.exe')) {
		Log(4, 'Everything is not running!!!');
		alert(tab.lister, DOpus.strings.Get('alert_no_Ev'));
		return;
	}

	util = DOpus.FSUtil();
	var lister = tab.lister;

	//=== DECIDE BETWEEN LOCAL OR GLOBAL SEARCH =======================================================
	var search_in = 'global';
	//user try to invoke local search by presing quick key twice
	if (query[0] == quickkey) {
		query = query.slice(1);
		//current tab path must exists and being a file system folder
		if (util.Exists(tab.path) && util.PathType(tab.path) === 'filesys') {
			Log(1, 'Enabled local search mode');
			search_in = tab.path.def_value;
		}
		else
			Log(3, 'local search mode can\'t be used for this path. Changing to global search mode');
	}
	//=== GETTING FLAGS VALUES ========================================================================
	var in_dest = g_flags & (1 << 0) ? true : false;
	var no_name = g_flags & (1 << 1) ? true : false;
	var diacritics = g_flags & (1 << 2) ? true : false;
	var use_case = g_flags & (1 << 3) ? true : false;
	var regex = g_flags & (1 << 4) ? true : false;
	var whole_words = g_flags & (1 << 5) ? true : false;
	//=== OBTAINING COLLECTION NAME ===================================================================
	util = DOpus.Create.StringTools();
	var query_as_name = no_name ? false : Script.config.query_as_name;
	var coll_name, cmdline, coll_name_pos;
	//=== SPECIAL USE OF '::' ========================================================================
	if (query.substring(0, 2) == '::') { //overrides query_as_name and set it to true
		query = query.slice(2);
		no_name = false;
	}
	if (no_name) coll_name = 'coll://' + util.LanguageStr('FindResults');
	else coll_name = 'coll://' + (Script.config.coll_name ? util.MakeLegal(Script.config.coll_name, 'fn') : 'Everything');
	Script.vars.Set('collection_name', coll_name);
	Script.vars('collection_name').persist = true;
	if (query_as_name) {
		try {
			coll_name += '/' + util.MakeLegal(query + '(' + util.Truncate(search_in, 30, 2) + ')', 'fn'); //Make path friendly the query in order to use it as filename 
			coll_name_pos = -1;
		}
		catch (e) {
			Log(3, 'Error when trying to get query as name:' + e.description);
		}
	}

	Log(2, 'Searching in        : ' + search_in);
	Log(2, 'Open in dest        : ' + in_dest);
	Log(2, 'collection name     : ' + coll_name);
	Log(1, 'flags quick key     : ' + flags_quickkey);
	Log(1, 'No named collection : ' + no_name);
	Log(1, 'Diacritics          : ' + diacritics);
	Log(1, 'Case sensitive      : ' + use_case);
	Log(1, 'Regular expressions : ' + regex);
	Log(1, 'Whole words         : ' + whole_words);
	Log(1, 'Use query as name   : ' + query_as_name);

	//=== PREPARING RESULTS COLLECTION ===============================================================
	// coll_name_pos holds the tab position (if any) for the results collection, in order to reuse it
	//coll_name_pos=-1 means that coll is not open or doesn't exists
	//coll_name_pos=-2 means that coll is open in the current tab
	//if coll is open, bring it to front and make it source for the command
	if (tab.path.def_value == coll_name) {
		in_dest = false;
		coll_name_pos = -2;
	}
	else if (!coll_name_pos) coll_name_pos = FindTab(coll_name, (in_dest) ? lister.tabsright : lister.tabsleft);
	Log(1, 'position collname   : ' + coll_name_pos);
	if (coll_name_pos >= 0) {
		if (in_dest) cmd.RunCommand('SET FOCUS=dest');
		cmdline = 'Go TABSELECT=' + coll_name_pos;
		Log(1, 'command             :' + cmdline);
		cmd.RunCommand(cmdline);
		//Update the lister and the source tab for the command
		lister.Update();
		cmd.SetSourceTab(lister.activetab);
	}
	//=== PREPARING COMMAND LINE =====================================================================
	saveQuery(query); //save query before any change;
	search_in = (search_in != 'global') ? ('IN "' + search_in + '"') : '';
	if (Script.config.flags_are_global) query = ((use_case) ? '::case:' : '::nocase:') + ((diacritics) ? '::diacritics:' : '::nodiacritics:') + ((regex) ? '::regex:' : '::noregex:') + ((whole_words) ? '::ww:' : '::noww:') + query;
	else query = ((use_case) ? 'case:' : 'nocase:') + ((diacritics) ? 'diacritics:' : 'nodiacritics:') + ((regex) ? 'regex:' : 'noregex:') + ((whole_words) ? 'ww:' : 'noww:') + query;
	Log(2, 'Everything query    : ' + query);
	cmdline = 'Find CLEAR ' + ((search_in) ? (search_in + ' QUERYENGINE=everything') : 'QUERYENGINE=everythingglobal');
	//No open tab for results, so we can open a new one
	if (coll_name_pos == -1) cmdline += ' SHOWRESULTS=' + ((in_dest) ? 'dest,tab' : 'source,tab');
	//The results collection is already open, just reuse it
	else cmdline += ' SHOWRESULTS=source';
	cmdline += ' COLLNAME="' + coll_name + '" QUERY ' + query;
	Log(1, 'command             :' + cmdline);
	cmd.RunCommand(cmdline);
	//Set focus to dest if a new tab was opened in there
	if (coll_name_pos == -1 && in_dest) cmd.RunCommand('SET FOCUS=dest');
	tab = null;
	cmd = null;
	util = null;
	lister = null;
	Log(2, 'FINISHED           : ' + (new Date().getTime() - ini) + 'ms');
	Log(2, '===================================================================');
}

//Dialog window to configure script flags
function configFlags(parent, flags) {
	var dlg = parent.dlg();
	dlg.template = 'configflags';
	dlg.disable_window = parent;
	dlg.detach = true;
	dlg.Create();
	for (var i = 0; i < 6; i++) {
		dlg.Control('flag' + i).label = DOpus.strings.Get('flag' + i);
		dlg.Control('flag' + i).value = flags & (1 << i);
	}
	dlg.title = script_name + ' v' + script_version + ' - Flags Configuration';
	dlg.Show();
	while (true) {
		msg = dlg.GetMsg();
		if (!msg.result) break;
		if (msg.event == 'click' && msg.control == 'ok_btn') dlg.EndDlg(1);
	}
	if (dlg.result == 1) {
		flags = 0;
		for (var i = 0; i < 6; i++) {
			if (dlg.Control('flag' + i).value) flags += (1 << i);
		}
		Script.UpdateFAYTFlags(script_name, flags);
	}
	dlg = null;
	Log(2, 'Flags saved in configuration!!');
}

//Find the first tab that matched with path
function FindTab(path, tabs) {
	if (!tabs) return -1;
	var tab;
	for (var i = 0; i < tabs.count; i++) {
		tab = tabs(i);
		if (tab.path.def_value == path) return i;
	}
	return -1;
}

//Delete Everything collection
function DeleteColl() {
	try {
		if (!Script.vars.Exists('collection_name')) return;
		var coll_name = Script.vars.Get('collection_name');
		if (DOpus.FSUtil().Exists(coll_name)) {
			var cmd = DOpus.Create.Command();
			Log(1, 'Deleting saved collection and their sub collections : ' + coll_name);
			cmd.RunCommand('Delete FILE="' + coll_name + '" QUIET');
			cmd = null;
		}
	}
	catch (e) {
		Log(3, 'Unable to delete collection :' + e.description);
	}
}

function OnScriptConfigChange(ConfigChangeData) {
	var opt = ConfigChangeData.changed;
	for (var i = 0; i < opt.length; i++) {
		if (opt(i) == 'query_as_name') DeleteColl();
	}
	opt = null;
}

function Log(level, text) {
	if (level === 4 || Script.config['log level'] < level) {
		if (level == 1) DOpus.Output('<#%vs_dragdrop_normal_action>DEBUG   => ' + text + '</#>');
		else if (level == 2) DOpus.Output('INFO    => ' + text);
		else if (level === 3) DOpus.Output('<#%vs_dragdrop_warning_action>WARNING => ' + text + '</#>');
		else DOpus.Output('ERROR   => ' + text, true);
	}
}

function saveQuery(query) {
	Log(2, 'Saving input        : ' + query);
	var values = DOpus.Create.Vector(query);
	if (Script.Vars.Exists('saved_queries')) values.append(Script.Vars.Get('saved_queries'));
	values.unique();
	var max = Script.config.max_saved_queries;
	if (max <= 0) max = 10;
	if (values.size > max) values.resize(10);
	Script.Vars.Set('saved_queries', values);
	Script.Vars('saved_queries').persist = true;
	return;
}

function getSuggestions(quick_key) {
	Log(1, 'Building suggestions: str => ' + quick_key);
	var values = DOpus.Create.Vector();
	try {
		if (Script.Vars.Exists('saved_queries')) {
			var vector = Script.Vars.Get('saved_queries');
			for (var i = 0; i < vector.length; i++) {
				values.push_back(quick_key + vector(i) + '\t(history)');
			}
			vector = null;
		}
	}
	catch (error) {
		Log(3, 'Error when getting suggestions : ' + error.description);
	};
	return values;
}

function CheckDefaultFilter() {
	Log(1, 'Trying to get default filter from prefs.oxc...');
	var result = false;
	try {
		var prefs_file = DOpus.Aliases('dopusdata').path + '\\ConfigFiles\\prefs.oxc';
		var xmlDocEv = new ActiveXObject('Msxml2.DOMDocument');
		xmlDocEv.load(prefs_file);
		if (xmlDocEv.parseError.errorCode == 0) {
			var default_filter = '';
			var fayts = xmlDocEv.selectSingleNode('//prefs/fayt');
			if (fayts) default_filter = fayts.getAttribute('defaultmode');
			Log(1, '   default filter : ' + default_filter);
			if (default_filter === script_name) result = true;
			if (!quickkey) {
				fayts = xmlDocEv.selectSingleNode('//prefs/fayt/modes/mode[@fayt_mode="' + script_name + '"]');
				if (fayts) quickkey = String.fromCharCode(fayts.getAttribute('key'));
			}
		}
		else
			Log(3, 'Error parsing prefs xml file');
		xmlDocEv = null;
	}
	catch (err) {
		Log(3, 'Error while trying to get default filter info : ' + err.description);
	};
	return result;
}

function alert(parent, message, level) {
	var dlg = DOpus.Dlg();
	if (parent) {
		dlg.window = parent;
		dlg.disable_window = parent;
	}
	dlg.message = message;
	dlg.buttons = '&OK';
	dlg.title = script_name + ' v' + script_version;
	if (level === 0) dlg.icon = 'info';
	else if (level === 1) dlg.icon = 'question';
	else if (level === 2) dlg.icon = 'warning';
	else dlg.icon = 'error';
	dlg.Show();
	dlg = null;
}
==SCRIPT RESOURCES
<resources>
	<resource type="strings">
		<strings lang="esm">
			<string id="alert_no_Ev">¡Everything no se está ejecutando!</string>
			<string id="coll_name">Nombre de la colección a utilizar.</string>
			<string id="debug">El nivel de registro a mostrar. OFF para mostrar solo errores. 
DEBUG para mostrar todos los mensajes. 
STANDARD para mostrar solo la información más relevante. 
WARNING para mostrar mensajes que necesitan tu atención.</string>
			<string id="delete_onstartup">Si se activa, se eliminarán todas las colecciones de resultados trás iniciar DOpus.
Solamente afecta a las colecciones creadas por este comando.</string>
			<string id="flag0">Resultados en nueva pes&amp;taña en destino</string>
			<string id="flag1">No usar colección con &amp;nombre</string>
			<string id="flag2">No ignorar &amp;diacríticos</string>
			<string id="flag3">Distinguir &amp;mayúsculas</string>
			<string id="flag4">E&amp;xpresiones regulares</string>
			<string id="flag5">Buscar por &amp;palabras completas</string>
			<string id="flags_are_global">Si se activa, las opciones de búsqueda (mayúsculas, regex, diacríticos, palabras completas) se aplican globalmente para el resto de la búsqueda.
En caso contrario, sólo afectan a la primera función de búsqueda.</string>
			<string id="flags_quick_key">Al escribir en el filtro FAYT el caracter introducido, se activará un diálogo interno para la configuración de flags.</string>
			<string id="max_saved_queries">Número máximo de búsquedas a recordar.</string>
			<string id="query_as_name">Si se activa, los resultados se guardarán como subcolecciones con la consulta como nombre.
Esto es útil sobre todo porque permite realizar múltiples búsquedas sin sobreescribir los resultados anteriores.</string>
			<string id="quick_config_key">Al escribir esta cadena en el filtro FAYT, se activará el cuadro de diálogo del script.</string>
		</strings>
		<strings lang="english">
			<string id="alert_no_Ev">Everything process is not running!</string>
			<string id="coll_name">Collection name to use for results.</string>
			<string id="debug">Logging level to be displayed. OFF to show only errors. 
DEBUG to show all messages. 
STANDARD to show only the most relevant information.
WARNING to show messages that needs your attention.</string>
			<string id="delete_onstartup">If true, all result collections will be deleted after starting DOpus.
It only affects collections created by this command.</string>
			<string id="flag0">Open results in a new &amp;tab (destination)</string>
			<string id="flag1">Don&apos;t use a &amp;named collection</string>
			<string id="flag2">Don&apos;t ignore &amp;diacritics</string>
			<string id="flag3">&amp;Case sensitive</string>
			<string id="flag4">Regular e&amp;xpressions</string>
			<string id="flag5">Search for &amp;whole words</string>
			<string id="flags_are_global">If true, flag search options (case, regex, diacritics, whole words) are applied globally for the rest of the search.
Otherwise, they only affect the first search function.</string>
			<string id="flags_quick_key">When typing this in the FAYT field, an internal dialog for editing search flags will be shown.</string>
			<string id="max_saved_queries">Maximum number of searches to be remembered.</string>
			<string id="query_as_name">If true, the results will be saved as sub-collections with the query as the name.
This is mainly useful because it allows multiple searches to be performed without overwriting previous results.</string>
			<string id="quick_config_key">When typing this value in the FAYT field, the script&apos;s own configuration dialog will be activated. </string>
		</strings>
	</resource>
	<resource name="configflags" type="dialog">
		<dialog height="104" lang="english" width="180">
			<languages>
				<language height="104" lang="esm" width="180" />
			</languages>
			<control height="10" name="flag0" type="check" width="170" x="4" y="4" />
			<control height="10" name="flag1" type="check" width="170" x="4" y="17" />
			<control height="10" name="flag2" type="check" width="170" x="4" y="30" />
			<control height="10" name="flag3" type="check" width="170" x="4" y="43" />
			<control height="10" name="flag4" type="check" width="170" x="4" y="56" />
			<control height="10" name="flag5" type="check" width="170" x="4" y="69" />
			<control height="14" name="ok_btn" title="Save" type="button" width="50" x="126" y="86">
				<languages>
					<language height="14" lang="esm" title="Guardar" width="50" x="126" y="86" />
				</languages>
			</control>
		</dialog>
	</resource>
</resources>
