OSDN Git Service

ddd54c09487e635c81a67fc65f223b9b8f83881d
[winmerge-jp/winmerge-jp.git] / Src / FileTransform.cpp
1 /////////////////////////////////////////////////////////////////////////////
2 //    WinMerge:  an interactive diff/merge utility
3 //    Copyright (C) 1997-2000  Thingamahoochie Software
4 //    Author: Dean Grimm
5 //    SPDX-License-Identifier: GPL-2.0-or-later
6 /////////////////////////////////////////////////////////////////////////////
7 /**
8  *  @file FileTransform.cpp
9  *
10  *  @brief Implementation of file transformations
11  */ 
12
13 #include "pch.h"
14 #include "FileTransform.h"
15 #include <vector>
16 #include <Poco/Exception.h>
17 #include <Poco/Mutex.h>
18 #include "Plugins.h"
19 #include "multiformatText.h"
20 #include "Environment.h"
21 #include "TFile.h"
22 #include "paths.h"
23 #include "MergeApp.h"
24
25 using Poco::Exception;
26
27 static Poco::FastMutex g_mutex;
28
29 ////////////////////////////////////////////////////////////////////////////////
30 // transformations : packing unpacking
31
32
33 std::vector<PluginForFile::PipelineItem> PluginForFile::ParsePluginPipeline(String& errorMessage) const
34 {
35         return ParsePluginPipeline(m_PluginPipeline, errorMessage);
36 }
37
38 std::vector<PluginForFile::PipelineItem> PluginForFile::ParsePluginPipeline(const String& pluginPipeline, String& errorMessage)
39 {
40         std::vector<PluginForFile::PipelineItem> result;
41         bool inQuotes = false;
42         TCHAR quoteChar = 0;
43         std::vector<String> args;
44         String token, name;
45         errorMessage.clear();
46         const TCHAR* p = pluginPipeline.c_str();
47         while (_istspace(*p)) p++;
48         while (*p)
49         {
50                 TCHAR sep = 0;
51                 while (*p)
52                 {
53                         if (!inQuotes)
54                         {
55                                 if (*p == '"' || *p == '\'')
56                                 {
57                                         inQuotes = true;
58                                         quoteChar = *p;
59                                 }
60                                 else if (_istspace(*p))
61                                 {
62                                         sep = *p;
63                                         break;
64                                 }
65                                 else if (*p == '|')
66                                 {
67                                         sep = *p;
68                                         break;
69                                 }
70                                 else
71                                         token += *p;
72                         }
73                         else
74                         {
75                                 if (*p == quoteChar)
76                                 {
77                                         if (*(p + 1) == quoteChar)
78                                         {
79                                                 token += *p;
80                                                 ++p;
81                                         }
82                                         else
83                                         {
84                                                 inQuotes = false;
85                                         }
86                                 }
87                                 else
88                                 {
89                                         token += *p;
90                                 }
91                         }
92                         ++p;
93                 }
94                 if (name.empty())
95                 {
96                         name = token;
97                 }
98                 else
99                 {
100                         args.push_back(token);
101                 }
102                 while (_istspace(*p)) p++;
103                 if (*p == '|')
104                         sep = *p;
105                 if (sep == '|')
106                         p++;
107                 token.clear();
108                 if (sep == '|' || !*p)
109                 {
110                         if (name.empty() || (sep == '|' && !*p))
111                         {
112                                 errorMessage = strutils::format_string1(_("Missing plugin name in plugin pipeline: %1"), pluginPipeline);
113                                 break;
114                         }
115                         result.push_back({ name, args, quoteChar });
116                         name.clear();
117                         args.clear();
118                         quoteChar = 0;
119                 }
120         };
121         if (inQuotes)
122                 errorMessage = strutils::format_string1(_("Missing quotation mark in plugin pipeline: %1"), pluginPipeline);
123         return result;
124 }
125
126 String PluginForFile::MakePluginPipeline(const std::vector<PluginForFile::PipelineItem>& list)
127 {
128         int i = 0;
129         String pipeline;
130         for (const auto& [name, args, quoteChar] : list)
131         {
132                 if (quoteChar && name.find_first_of(_T(" '\"")) != String::npos)
133                 {
134                         String nameQuoted = name;
135                         strutils::replace(nameQuoted, String(1, quoteChar), String(2, quoteChar));
136                         pipeline += strutils::format(_T("%c%s%c"), quoteChar, nameQuoted, quoteChar);
137                 }
138                 else
139                 {
140                         pipeline += name;
141                 }
142                 if (!args.empty())
143                 {
144                         for (const auto& arg : args)
145                         {
146                                 if (quoteChar)
147                                 {
148                                         String argQuoted = arg;
149                                         strutils::replace(argQuoted, String(1, quoteChar), String(2, quoteChar));
150                                         pipeline += strutils::format(_T(" %c%s%c"), quoteChar, argQuoted, quoteChar);
151                                 }
152                                 else
153                                 {
154                                         pipeline += _T(" ") + arg;
155                                 }
156                         }
157                 }
158                 if (i < list.size() - 1)
159                         pipeline += _T("|");
160                 i++;
161         }
162         return pipeline;
163 }
164
165 String PluginForFile::MakeArguments(const std::vector<String>& args, const std::vector<StringView>& variables)
166 {
167         String newstr;
168         int i = 0;
169         for (const auto& arg : args)
170         {
171                 String newarg;
172                 for (const TCHAR* p = arg.c_str(); *p; ++p)
173                 {
174                         if (*p == '%' && *(p + 1) != 0)
175                         {
176                                 ++p;
177                                 TCHAR c = *p;
178                                 if (c == '%')
179                                 {
180                                         newarg += '%';
181                                 }
182                                 else if (c >= '1' && c <= '9')
183                                 {
184                                         if ((c - '1') < variables.size())
185                                                 newarg += String{ variables[(c - '1')].data(), variables[(c - '1')].length() };
186                                 }
187                                 else
188                                 {
189                                         newarg += *(p - 1);
190                                         newarg += c;
191                                 }
192                         }
193                         else
194                         {
195                                 newarg += *p;
196                         }
197                 }
198                 if (newarg.find_first_of(_T(" \"")) != String::npos)
199                 {
200                         strutils::replace(newarg, _T("\""), _T("\"\""));
201                         newstr += _T("\"") + newarg + _T("\"");
202                 }
203                 else
204                 {
205                         newstr += newarg;
206                 }
207                 if (i < args.size() - 1)
208                         newstr += ' ';
209                 i++;
210         }
211         return newstr;
212 }
213
214 bool PackingInfo::GetPackUnpackPlugin(const String& filteredFilenames, bool bReverse,
215         std::vector<std::tuple<PluginInfo*, std::vector<String>, bool>>& plugins,
216         String *pPluginPipelineResolved, String& errorMessage) const
217 {
218         auto result = ParsePluginPipeline(errorMessage);
219         if (!errorMessage.empty())
220                 return false;
221         std::vector<PluginForFile::PipelineItem> pipelineResolved;
222         for (auto& [pluginName, args, quoteChar] : result)
223         {
224                 PluginInfo* plugin = nullptr;
225                 bool bWithFile = true;
226                 if (pluginName == _T("<None>") || pluginName == _("<None>"))
227                         ;
228                 else if (pluginName == _T("<Automatic>") || pluginName == _("<Automatic>"))
229                 {
230                         plugin = CAllThreadsScripts::GetActiveSet()->GetAutomaticPluginByFilter(L"FILE_PACK_UNPACK", filteredFilenames);
231                         if (plugin == nullptr)
232                                 plugin = CAllThreadsScripts::GetActiveSet()->GetAutomaticPluginByFilter(L"FILE_FOLDER_PACK_UNPACK", filteredFilenames);
233                         if (plugin == nullptr)
234                         {
235                                 plugin = CAllThreadsScripts::GetActiveSet()->GetAutomaticPluginByFilter(L"BUFFER_PACK_UNPACK", filteredFilenames);
236                                 bWithFile = false;
237                         }
238                 }
239                 else
240                 {
241                         plugin = CAllThreadsScripts::GetActiveSet()->GetPluginByName(L"FILE_PACK_UNPACK", pluginName);
242                         if (plugin == nullptr)
243                         {
244                                 plugin = CAllThreadsScripts::GetActiveSet()->GetPluginByName(L"FILE_FOLDER_PACK_UNPACK", pluginName);
245                                 if (plugin == nullptr)
246                                 {
247                                         plugin = CAllThreadsScripts::GetActiveSet()->GetPluginByName(L"BUFFER_PACK_UNPACK", pluginName);
248                                         if (plugin == nullptr)
249                                         {
250                                                 plugin = CAllThreadsScripts::GetActiveSet()->GetPluginByName(nullptr, pluginName);
251                                                 if (plugin == nullptr)
252                                                 {
253                                                         errorMessage = strutils::format_string1(_("Plugin not found or invalid: %1"), pluginName);
254                                                 }
255                                                 else
256                                                 {
257                                                         plugin = nullptr;
258                                                         errorMessage = strutils::format(_T("'%s' is not PACK_UNPACK plugin"), pluginName);
259                                                 }
260                                                 return false;
261                                         }
262                                         bWithFile = false;
263                                 }
264                         }
265                 }
266                 if (plugin)
267                 {
268                         pipelineResolved.push_back({plugin->m_name, args, quoteChar });
269                         if (bReverse)
270                                 plugins.insert(plugins.begin(), { plugin, args, bWithFile });
271                         else
272                                 plugins.push_back({ plugin, args, bWithFile });
273                 }
274         }
275         if (pPluginPipelineResolved)
276                 *pPluginPipelineResolved = MakePluginPipeline(pipelineResolved);
277         return true;
278 }
279
280 // known handler
281 bool PackingInfo::Packing(String & filepath, const std::vector<int>& handlerSubcodes, const std::vector<StringView>& variables) const
282 {
283         // no handler : return true
284         if (m_PluginPipeline.empty())
285                 return true;
286
287         // control value
288         String errorMessage;
289         std::vector<std::tuple<PluginInfo*, std::vector<String>, bool>> plugins;
290         if (!GetPackUnpackPlugin(_T(""), true, plugins, nullptr, errorMessage))
291         {
292                 AppErrorMessageBox(errorMessage);
293                 return false;
294         }
295
296         auto itSubcode = handlerSubcodes.rbegin();
297         for (auto& [plugin, args, bWithFile] : plugins)
298         {
299                 bool bHandled = false;
300                 storageForPlugins bufferData;
301                 bufferData.SetDataFileAnsi(filepath);
302
303                 LPDISPATCH piScript = plugin->m_lpDispatch;
304                 Poco::FastMutex::ScopedLock lock(g_mutex);
305
306                 if (plugin->m_hasVariablesProperty)
307                 {
308                         if (!plugin::InvokePutPluginVariables(String(variables[0].data(), variables[0].length()), piScript))
309                                 return false;
310                 }
311                 if (plugin->m_hasArgumentsProperty)
312                 {
313                         if (!plugin::InvokePutPluginArguments(args.empty() ? plugin->m_arguments : MakeArguments(args, variables), piScript))
314                                 return false;
315                 }
316
317                 if (bWithFile)
318                 {
319                         // use a temporary dest name
320                         String srcFileName = bufferData.GetDataFileAnsi(); // <-Call order is important
321                         String dstFileName = bufferData.GetDestFileName(); // <-Call order is important
322                         bHandled = plugin::InvokePackFile(srcFileName,
323                                 dstFileName,
324                                 bufferData.GetNChanged(),
325                                 piScript, *itSubcode);
326                         if (bHandled)
327                                 bufferData.ValidateNewFile();
328                 }
329                 else
330                 {
331                         bHandled = plugin::InvokePackBuffer(*bufferData.GetDataBufferAnsi(),
332                                 bufferData.GetNChanged(),
333                                 piScript, *itSubcode);
334                         if (bHandled)
335                                 bufferData.ValidateNewBuffer();
336                 }
337
338                 // if this packer does not work, that is an error
339                 if (!bHandled)
340                         return false;
341
342                 // if the buffer changed, write it before leaving
343                 if (bufferData.GetNChangedValid() > 0)
344                 {
345                         bool bSuccess = bufferData.SaveAsFile(filepath);
346                         if (!bSuccess)
347                                 return false;
348                 }
349                 ++itSubcode;
350         }
351         return true;
352 }
353
354 bool PackingInfo::Packing(const String& srcFilepath, const String& dstFilepath, const std::vector<int>& handlerSubcodes, const std::vector<StringView>& variables) const
355 {
356         String csTempFileName = srcFilepath;
357         if (!Packing(csTempFileName, handlerSubcodes, variables))
358                 return false;
359         try
360         {
361                 TFile file1(csTempFileName);
362                 file1.copyTo(dstFilepath);
363                 if (srcFilepath!= csTempFileName)
364                         file1.remove();
365                 return true;
366         }
367         catch (Poco::Exception& e)
368         {
369                 LogErrorStringUTF8(e.displayText());
370                 return false;
371         }
372 }
373
374 bool PackingInfo::Unpacking(std::vector<int> * handlerSubcodes, String & filepath, const String& filteredText, const std::vector<StringView>& variables)
375 {
376         // no handler : return true
377         if (m_PluginPipeline.empty())
378                 return true;
379
380         // control value
381         String errorMessage;
382         std::vector<std::tuple<PluginInfo*, std::vector<String>, bool>> plugins;
383         if (!GetPackUnpackPlugin(filteredText, false, plugins, &m_PluginPipeline, errorMessage))
384         {
385                 AppErrorMessageBox(errorMessage);
386                 return false;
387         }
388
389         for (auto& [plugin, args, bWithFile] : plugins)
390         {
391                 if (plugin->m_argumentsRequired && args.empty())
392                 {
393
394                 }
395         }
396
397         if (handlerSubcodes)
398                 handlerSubcodes->clear();
399
400         for (auto& [plugin, args, bWithFile] : plugins)
401         {
402                 bool bHandled = false;
403                 storageForPlugins bufferData;
404                 bufferData.SetDataFileAnsi(filepath);
405
406                 // temporary subcode 
407                 int subcode = 0;
408
409                 LPDISPATCH piScript = plugin->m_lpDispatch;
410                 Poco::FastMutex::ScopedLock lock(g_mutex);
411
412                 if (plugin->m_hasVariablesProperty)
413                 {
414                         if (!plugin::InvokePutPluginVariables(String(variables[0].data(), variables[0].length()), piScript))
415                                 return false;
416                 }
417                 if (plugin->m_hasArgumentsProperty)
418                 {
419                         if (!plugin::InvokePutPluginArguments(args.empty() ? plugin->m_arguments : MakeArguments(args, variables), piScript))
420                                 return false;
421                 }
422
423                 if (bWithFile)
424                 {
425                         // use a temporary dest name
426                         bufferData.SetDestFileExtension(!plugin->m_ext.empty() ? plugin->m_ext : paths::FindExtension(filepath));
427                         String srcFileName = bufferData.GetDataFileAnsi(); // <-Call order is important
428                         String dstFileName = bufferData.GetDestFileName(); // <-Call order is important
429                         bHandled = plugin::InvokeUnpackFile(srcFileName,
430                                 dstFileName,
431                                 bufferData.GetNChanged(),
432                                 piScript, subcode);
433                         if (bHandled)
434                                 bufferData.ValidateNewFile();
435                 }
436                 else
437                 {
438                         bHandled = plugin::InvokeUnpackBuffer(*bufferData.GetDataBufferAnsi(),
439                                 bufferData.GetNChanged(),
440                                 piScript, subcode);
441                         if (bHandled)
442                                 bufferData.ValidateNewBuffer();
443                 }
444
445                 // if this unpacker does not work, that is an error
446                 if (!bHandled)
447                         return false;
448
449                 // valid the subcode
450                 if (handlerSubcodes)
451                         handlerSubcodes->push_back(subcode);
452
453                 // if the buffer changed, write it before leaving
454                 if (bufferData.GetNChangedValid() > 0)
455                 {
456                         bool bSuccess = bufferData.SaveAsFile(filepath);
457                         if (!bSuccess)
458                                 return false;
459                 }
460         }
461         return true;
462 }
463
464 String PackingInfo::GetUnpackedFileExtension(const String& filteredFilenames) const
465 {
466         String ext;
467         String errorMessage;
468         std::vector<std::tuple<PluginInfo*, std::vector<String>, bool>> plugins;
469         if (GetPackUnpackPlugin(filteredFilenames, false, plugins, nullptr, errorMessage))
470         {
471                 for (auto& [plugin, args, bWithFile] : plugins)
472                         ext += plugin->m_ext;
473         }
474         return ext;
475 }
476
477 ////////////////////////////////////////////////////////////////////////////////
478 // transformation prediffing
479
480 bool PrediffingInfo::GetPrediffPlugin(const String& filteredFilenames, bool bReverse,
481         std::vector<std::tuple<PluginInfo*, std::vector<String>, bool>>& plugins,
482         String *pPluginPipelineResolved, String& errorMessage) const
483 {
484         auto result = ParsePluginPipeline(errorMessage);
485         if (!errorMessage.empty())
486                 return false;
487         std::vector<PluginForFile::PipelineItem> pipelineResolved;
488         for (auto& [pluginName, args, quoteChar] : result)
489         {
490                 PluginInfo* plugin = nullptr;
491                 bool bWithFile = true;
492                 if (pluginName == _T("<None>") || pluginName == _("<None>"))
493                         ;
494                 else if (pluginName == _T("<Automatic>") || pluginName == _("<Automatic>"))
495                 {
496                         plugin = CAllThreadsScripts::GetActiveSet()->GetAutomaticPluginByFilter(L"FILE_PREDIFF", filteredFilenames);
497                         if (plugin == nullptr)
498                         {
499                                 plugin = CAllThreadsScripts::GetActiveSet()->GetAutomaticPluginByFilter(L"BUFFER_PREDIFF", filteredFilenames);
500                                 if (plugin)
501                                         bWithFile = false;
502                         }
503                 }
504                 else
505                 {
506                         plugin = CAllThreadsScripts::GetActiveSet()->GetPluginByName(L"FILE_PREDIFF", pluginName);
507                         if (plugin == nullptr)
508                         {
509                                 plugin = CAllThreadsScripts::GetActiveSet()->GetPluginByName(L"BUFFER_PREDIFF", pluginName);
510                                 if (plugin == nullptr)
511                                 {
512                                         plugin = CAllThreadsScripts::GetActiveSet()->GetPluginByName(nullptr, pluginName);
513                                         if (plugin == nullptr)
514                                         {
515                                                 errorMessage = strutils::format_string1(_("Plugin not found or invalid: %1"), pluginName);
516                                         }
517                                         else
518                                         {
519                                                 errorMessage = strutils::format(_T("'%s' is not PREDIFF plugin"), pluginName);
520                                         }
521                                         return false;
522                                 }
523                                 bWithFile = false;
524                         }
525                 }
526                 if (plugin)
527                 {
528                         pipelineResolved.push_back({ plugin->m_name, args, quoteChar });
529                         if (bReverse)
530                                 plugins.insert(plugins.begin(), { plugin, args, bWithFile });
531                         else
532                                 plugins.push_back({ plugin, args, bWithFile });
533                 }
534         }
535         if (pPluginPipelineResolved)
536                 *pPluginPipelineResolved = MakePluginPipeline(pipelineResolved);
537         return true;
538 }
539
540 bool PrediffingInfo::Prediffing(String & filepath, const String& filteredText, bool bMayOverwrite, const std::vector<StringView>& variables)
541 {
542         // no handler : return true
543         if (m_PluginPipeline.empty())
544                 return true;
545
546         // control value
547         bool bHandled = false;
548         String errorMessage;
549         std::vector<std::tuple<PluginInfo*, std::vector<String>, bool>> plugins;
550         if (!GetPrediffPlugin(filteredText, false, plugins, &m_PluginPipeline, errorMessage))
551         {
552                 AppErrorMessageBox(errorMessage);
553                 return false;
554         }
555
556         for (const auto& [plugin, args, bWithFile] : plugins)
557         {
558                 storageForPlugins bufferData;
559                 // detect Ansi or Unicode file
560                 bufferData.SetDataFileUnknown(filepath, bMayOverwrite);
561                 // TODO : set the codepage
562                 // bufferData.SetCodepage();
563
564                 LPDISPATCH piScript = plugin->m_lpDispatch;
565                 Poco::FastMutex::ScopedLock lock(g_mutex);
566
567                 if (plugin->m_hasVariablesProperty)
568                 {
569                         if (!plugin::InvokePutPluginVariables(String(variables[0].data(), variables[0].length()), piScript))
570                                 return false;
571                 }
572                 if (plugin->m_hasArgumentsProperty)
573                 {
574                         if (!plugin::InvokePutPluginArguments(args.empty() ? plugin->m_arguments : MakeArguments(args, variables), piScript))
575                                 return false;
576                 }
577
578                 if (bWithFile)
579                 {
580                         // use a temporary dest name
581                         String srcFileName = bufferData.GetDataFileAnsi(); // <-Call order is important
582                         String dstFileName = bufferData.GetDestFileName(); // <-Call order is important
583                         bHandled = plugin::InvokePrediffFile(srcFileName,
584                                 dstFileName,
585                                 bufferData.GetNChanged(),
586                                 piScript);
587                         if (bHandled)
588                                 bufferData.ValidateNewFile();
589                 }
590                 else
591                 {
592                         // probably it is for VB/VBscript so use a BSTR as argument
593                         bHandled = plugin::InvokePrediffBuffer(*bufferData.GetDataBufferUnicode(),
594                                 bufferData.GetNChanged(),
595                                 piScript);
596                         if (bHandled)
597                                 bufferData.ValidateNewBuffer();
598                 }
599
600                 // if this unpacker does not work, that is an error
601                 if (!bHandled)
602                         return false;
603
604                 // if the buffer changed, write it before leaving
605                 if (bufferData.GetNChangedValid() > 0)
606                 {
607                         // bufferData changes filepath here to temp filepath
608                         bool bSuccess = bufferData.SaveAsFile(filepath);
609                         if (!bSuccess)
610                                 return false;
611                 }
612         }
613         return true;
614 }
615
616 namespace FileTransform
617 {
618
619 bool AutoUnpacking = false;
620 bool AutoPrediffing = false;
621
622 ////////////////////////////////////////////////////////////////////////////////
623
624 bool AnyCodepageToUTF8(int codepage, String & filepath, bool bMayOverwrite)
625 {
626         String tempDir = env::GetTemporaryPath();
627         if (tempDir.empty())
628                 return false;
629         String tempFilepath = env::GetTemporaryFileName(tempDir, _T("_W3"));
630         if (tempFilepath.empty())
631                 return false;
632         // TODO : is it better with the BOM or without (just change the last argument)
633         int nFileChanged = 0;
634         bool bSuccess = ::AnyCodepageToUTF8(codepage, filepath, tempFilepath, nFileChanged, false); 
635         if (bSuccess && nFileChanged!=0)
636         {
637                 // we do not overwrite so we delete the old file
638                 if (bMayOverwrite)
639                 {
640                         try
641                         {
642                                 TFile(filepath).remove();
643                         }
644                         catch (Exception& e)
645                         {
646                                 LogErrorStringUTF8(e.displayText());
647                         }
648                 }
649                 // and change the filepath if everything works
650                 filepath = tempFilepath;
651         }
652         else
653         {
654                 try
655                 {
656                         TFile(tempFilepath).remove();
657                 }
658                 catch (Exception& e)
659                 {
660                         LogErrorStringUTF8(e.displayText());
661                 }
662         }
663
664         return bSuccess;
665 }
666
667
668 ////////////////////////////////////////////////////////////////////////////////
669 // transformation : TextTransform_Interactive (editor scripts)
670
671 std::vector<String> GetFreeFunctionsInScripts(const wchar_t *TransformationEvent)
672 {
673         std::vector<String> sNamesArray;
674
675         // get an array with the available scripts
676         PluginArray * piScriptArray = 
677                 CAllThreadsScripts::GetActiveSet()->GetAvailableScripts(TransformationEvent);
678
679         // fill in these structures
680         int nFnc = 0;   
681         size_t iScript;
682         for (iScript = 0 ; iScript < piScriptArray->size() ; iScript++)
683         {
684                 const PluginInfoPtr & plugin = piScriptArray->at(iScript);
685                 if (plugin->m_disabled)
686                         continue;
687                 LPDISPATCH piScript = plugin->m_lpDispatch;
688                 std::vector<String> scriptNamesArray;
689                 std::vector<int> scriptIdsArray;
690                 int nScriptFnc = plugin::GetMethodsFromScript(piScript, scriptNamesArray, scriptIdsArray);
691                 sNamesArray.resize(nFnc+nScriptFnc);
692
693                 int iFnc;
694                 for (iFnc = 0 ; iFnc < nScriptFnc ; iFnc++)
695                         sNamesArray[nFnc+iFnc] = scriptNamesArray[iFnc];
696
697                 nFnc += nScriptFnc;
698         }
699         return sNamesArray;
700 }
701
702 bool Interactive(String & text, const std::vector<String>& args, const wchar_t *TransformationEvent, int iFncChosen, const std::vector<StringView>& variables)
703 {
704         if (iFncChosen < 0)
705                 return false;
706
707         // get an array with the available scripts
708         PluginArray * piScriptArray = 
709                 CAllThreadsScripts::GetActiveSet()->GetAvailableScripts(TransformationEvent);
710
711         size_t iScript;
712         for (iScript = 0 ; iScript < piScriptArray->size() ; iScript++)
713         {
714                 if (iFncChosen < piScriptArray->at(iScript)->m_nFreeFunctions)
715                         // we have found the script file
716                         break;
717                 iFncChosen -= piScriptArray->at(iScript)->m_nFreeFunctions;
718         }
719
720         if (iScript >= piScriptArray->size())
721                 return false;
722
723         PluginInfo* plugin = piScriptArray->at(iScript).get();
724
725         // iFncChosen is the index of the function in the script file
726         // we must convert it to the function ID
727         int fncID = plugin::GetMethodIDInScript(plugin->m_lpDispatch, iFncChosen);
728
729         if (plugin->m_hasVariablesProperty)
730         {
731                 if (!plugin::InvokePutPluginVariables(String(variables[0].data(), variables[0].length()), plugin->m_lpDispatch))
732                         return false;
733         }
734         if (plugin->m_hasArgumentsProperty)
735         {
736                 if (!plugin::InvokePutPluginArguments(args.empty() ? plugin->m_arguments : PluginForFile::MakeArguments(args, variables), plugin->m_lpDispatch))
737                         return false;
738         }
739
740         // execute the transform operation
741         int nChanged = 0;
742         plugin::InvokeTransformText(text, nChanged, plugin->m_lpDispatch, fncID);
743
744         return (nChanged != 0);
745 }
746
747 std::pair<
748         std::vector<std::tuple<String, String, unsigned, PluginInfo *>>,
749         std::map<String, std::vector<std::tuple<String, String, unsigned, PluginInfo *>>>
750 >
751 CreatePluginMenuInfos(const String& filteredFilenames, const std::vector<std::wstring>& events, unsigned baseId)
752 {
753         std::vector<std::tuple<String, String, unsigned, PluginInfo *>> suggestedPlugins;
754         std::map<String, std::vector<std::tuple<String, String, unsigned, PluginInfo *>>> allPlugins;
755         std::map<String, int> captions;
756         unsigned id = baseId;
757         bool addedNoneAutomatic = false;
758         for (const auto& event: events)
759         {
760                 auto pScriptArray =
761                         CAllThreadsScripts::GetActiveSet()->GetAvailableScripts(event.c_str());
762                 for (auto& plugin : *pScriptArray)
763                 {
764                         if (!plugin->m_disabled)
765                         {
766                                 if (event != L"EDITOR_SCRIPT")
767                                 {
768                                         if (!addedNoneAutomatic)
769                                         {
770                                                 String process = _T("");
771                                                 allPlugins.insert_or_assign(process, std::vector<std::tuple<String, String, unsigned, PluginInfo *>>());
772                                                 allPlugins[process].emplace_back(_("<None>"), _T(""), id++, plugin.get());
773                                                 allPlugins[process].emplace_back(_("<Automatic>"), _T("<Automatic>"), id++, plugin.get());
774                                                 addedNoneAutomatic = true;
775                                         }
776                                         const auto menuCaption = plugin->GetExtendedPropertyValue(_T("MenuCaption"));
777                                         const auto processType = plugin->GetExtendedPropertyValue(_T("ProcessType"));
778                                         const String caption = tr(ucr::toUTF8(menuCaption.has_value() ?
779                                                 String{ menuCaption->data(), menuCaption->size() } : plugin->m_name));
780                                         const String process = tr(ucr::toUTF8(processType.has_value() ?
781                                                 String{ processType->data(), processType->size() } : _T("&Others")));
782
783                                         if (plugin->TestAgainstRegList(filteredFilenames))
784                                                 suggestedPlugins.emplace_back(caption, plugin->m_name, id, plugin.get());
785
786                                         if (allPlugins.find(process) == allPlugins.end())
787                                                 allPlugins.insert_or_assign(process, std::vector<std::tuple<String, String, unsigned, PluginInfo *>>());
788                                         allPlugins[process].emplace_back(caption, plugin->m_name, id, plugin.get());
789                                         captions[caption]++;
790                                         id++;
791                                 }
792                                 else
793                                 {
794                                         LPDISPATCH piScript = plugin->m_lpDispatch;
795                                         std::vector<String> scriptNamesArray;
796                                         std::vector<int> scriptIdsArray;
797                                         int nScriptFnc = plugin::GetMethodsFromScript(piScript, scriptNamesArray, scriptIdsArray);
798                                         bool matched = plugin->TestAgainstRegList(filteredFilenames);
799                                         for (int i = 0; i < nScriptFnc; ++i, ++id)
800                                         {
801                                                 const auto menuCaption = plugin->GetExtendedPropertyValue(scriptNamesArray[i] + _T(".MenuCaption"));
802                                                 auto processType = plugin->GetExtendedPropertyValue(scriptNamesArray[i] + _T(".ProcessType"));
803                                                 if (!processType.has_value())
804                                                         processType = plugin->GetExtendedPropertyValue(_T("ProcessType"));
805                                                 const String caption = tr(ucr::toUTF8(menuCaption.has_value() ?
806                                                         String{ menuCaption->data(), menuCaption->size() } : scriptNamesArray[i]));
807                                                 const String process = tr(ucr::toUTF8(processType.has_value() ?
808                                                         String{ processType->data(), processType->size() } : _T("&Others")));
809                                                 if (matched)
810                                                         suggestedPlugins.emplace_back(caption, plugin->m_name, id, plugin.get());
811                                                 if (allPlugins.find(process) == allPlugins.end())
812                                                         allPlugins.insert_or_assign(process, std::vector<std::tuple<String, String, unsigned, PluginInfo *>>());
813                                                 allPlugins[process].emplace_back(caption, plugin->m_name, id, plugin.get());
814                                         }
815                                 }
816                         }
817                 }
818         }
819         auto ResolveConflictMenuCaptions = [&captions](auto& plugins)
820         {
821                 for (auto& plugin : plugins)
822                 {
823                         const String& caption = std::get<0>(plugin);
824                         if (captions[caption] > 1)
825                                 std::get<0>(plugin) = caption + _T("(") + std::get<1>(plugin) + _T(")");
826                 }
827         };
828         ResolveConflictMenuCaptions(suggestedPlugins);
829         for (auto& [processType, plugins] : allPlugins)
830                 ResolveConflictMenuCaptions(plugins);
831         return { suggestedPlugins, allPlugins };
832 }
833
834 }
835
836 ////////////////////////////////////////////////////////////////////////////////