1 // -*- mode: c++; c-file-style: "linux"; c-basic-offset: 2; indent-tabs-mode: nil -*-
2 //
3 // Copyright (C) 2009-2015 Andrej Vodopivec <andrej.vodopivec@gmail.com>
4 // Copyright (C) 2015-2019 Gunter Königsmann <wxMaxima@physikbuch.de>
5 //
6 // This program is free software; you can redistribute it and/or modify
7 // it under the terms of the GNU General Public License as published by
8 // the Free Software Foundation; either version 2 of the License, or
9 // (at your option) any later version.
10 //
11 // This program is distributed in the hope that it will be useful,
12 // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 // GNU General Public License for more details.
15 //
16 //
17 // You should have received a copy of the GNU General Public License
18 // along with this program; if not, write to the Free Software
19 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
20 //
21 // SPDX-License-Identifier: GPL-2.0+
22
23 /*! \file
24 This file defines the class AutoComplete.
25
26 AutoComplete creates the list of autocompletions for a string and allows
27 dynamically appending maxima commands to this list as soon as they are defined.
28 */
29
30 #include <wx/sstream.h>
31 #include "Autocomplete.h"
32 #include "Dirstructure.h"
33 #include "Version.h"
34 #include <wx/textfile.h>
35 #include <wx/filename.h>
36 #include <wx/xml/xml.h>
37 #include "ErrorRedirector.h"
38 #include <wx/wfstream.h>
39 #include <wx/txtstrm.h>
40
AutoComplete(Configuration * configuration)41 AutoComplete::AutoComplete(Configuration *configuration)
42 {
43 m_configuration = configuration;
44 }
45
ClearWorksheetWords()46 void AutoComplete::ClearWorksheetWords()
47 {
48 #ifdef HAVE_OPENMP_TASKS
49 #pragma omp critical (AutocompleteBuiltins)
50 #endif
51 m_worksheetWords.clear();
52 }
53
ClearDemofileList()54 void AutoComplete::ClearDemofileList()
55 {
56 #ifdef HAVE_OPENMP_TASKS
57 #pragma omp critical (AutocompleteFiles)
58 #endif
59 m_wordList[demofile] = m_builtInDemoFiles;
60 }
61
AddSymbols(wxString xml)62 void AutoComplete::AddSymbols(wxString xml)
63 {
64 #ifdef HAVE_OPENMP_TASKS
65 wxLogMessage(_("Starting a background task that compiles a new list of autocompletible maxima commands."));
66 #pragma omp task
67 #endif
68 AddSymbols_Backgroundtask(xml);
69 }
70
AddSymbols_Backgroundtask(wxString xml)71 void AutoComplete::AddSymbols_Backgroundtask(wxString xml)
72 {
73 #ifdef HAVE_OPENMP_TASKS
74 #pragma omp critical (AutocompleteBuiltins)
75 #endif
76 {
77 wxXmlDocument xmldoc;
78 wxStringInputStream xmlStream(xml);
79 xmldoc.Load(xmlStream, wxT("UTF-8"));
80 wxXmlNode *node = xmldoc.GetRoot();
81 if(node != NULL)
82 {
83 wxXmlNode *children = node->GetChildren();
84 while (children != NULL)
85 {
86 if(children->GetType() == wxXML_ELEMENT_NODE)
87 {
88 if (children->GetName() == wxT("function"))
89 {
90 wxXmlNode *val = children->GetChildren();
91 if(val)
92 {
93 wxString name = val->GetContent();
94 AddSymbol_nowait(name, command);
95 }
96 }
97
98 if (children->GetName() == wxT("template"))
99 {
100 wxXmlNode *val = children->GetChildren();
101 if(val)
102 {
103 wxString name = val->GetContent();
104 AddSymbol_nowait(name, tmplte);
105 }
106 }
107
108 if (children->GetName() == wxT("unit"))
109 {
110 wxXmlNode *val = children->GetChildren();
111 if(val)
112 {
113 wxString name = val->GetContent();
114 AddSymbol_nowait(name, unit);
115 }
116 }
117
118 if (children->GetName() == wxT("value"))
119 {
120 wxXmlNode *val = children->GetChildren();
121 if(val)
122 {
123 wxString name = val->GetContent();
124 AddSymbol_nowait(name, command);
125 }
126 }
127 }
128 children = children->GetNext();
129 }
130 }
131 }
132 }
AddWorksheetWords(wxArrayString wordlist)133 void AutoComplete::AddWorksheetWords(wxArrayString wordlist)
134 {
135 #ifdef HAVE_OPENMP_TASKS
136 #pragma omp critical (AutocompleteBuiltins)
137 #endif
138 {
139 wxArrayString::const_iterator it;
140 for (it = wordlist.begin(); it != wordlist.end(); ++it)
141 m_worksheetWords[*it] = 1;
142 }
143 }
144
~AutoComplete()145 AutoComplete::~AutoComplete()
146 {
147 #ifdef HAVE_OPENMP_TASKS
148 #pragma omp taskwait
149 #endif
150 }
151
LoadSymbols()152 void AutoComplete::LoadSymbols()
153 {
154 #ifdef HAVE_OPENMP_TASKS
155 wxLogMessage(_("Starting a background task that setups the autocomplete builtins list."));
156 #pragma omp task
157 #endif
158 BuiltinSymbols_BackgroundTask();
159 #ifdef HAVE_OPENMP_TASKS
160 wxLogMessage(_("Starting a background task that setups the autocompletable files list."));
161 #pragma omp task
162 #endif
163 LoadSymbols_BackgroundTask();
164 }
165
BuiltinSymbols_BackgroundTask()166 void AutoComplete::BuiltinSymbols_BackgroundTask()
167 {
168 #ifdef HAVE_OPENMP_TASKS
169 #pragma omp critical (AutocompleteBuiltins)
170 #endif
171 {
172 m_wordList[command].Clear();
173 m_wordList[tmplte].Clear();
174 m_wordList[esccommand].Clear();
175 m_wordList[unit].Clear();
176
177 LoadBuiltinSymbols();
178
179 for(Configuration::StringHash::const_iterator it = m_configuration->m_escCodes.begin();
180 it != m_configuration->m_escCodes.end();
181 ++it)
182 m_wordList[esccommand].Add(it->first);
183 m_wordList[command].Sort();
184 m_wordList[tmplte].Sort();
185 m_wordList[unit].Sort();
186 m_wordList[esccommand].Sort();
187
188 wxString line;
189
190 /// Load private symbol list (do something different on Windows).
191 wxString privateList;
192 privateList = Dirstructure::Get()->UserAutocompleteFile();
193 wxLogMessage(wxString::Format(
194 _("Trying to load a list of autocompletible symbols from file %s"),
195 privateList.utf8_str()));
196 if (wxFileExists(privateList))
197 {
198 wxTextFile priv(privateList);
199
200 priv.Open();
201
202 wxRegEx function("^[fF][uU][nN][cC][tT][iI][oO][nN] *: *");
203 wxRegEx option ("^[oO][pP][tT][iI][oO][nN] *: *");
204 wxRegEx templte ("^[tT][eE][mM][pP][lL][aA][tT][eE] *: *");
205 wxRegEx unt ("^[uU][nN][iI][tT] *: *");
206 for (line = priv.GetFirstLine(); !priv.Eof(); line = priv.GetNextLine())
207 {
208 line.Trim(true);
209 line.Trim(false);
210 if(!line.StartsWith("#"))
211 {
212 if (function.Replace(&line, ""))
213 m_wordList[command].Add(line);
214 else if (option.Replace(&line, ""))
215 m_wordList[command].Add(line);
216 else if (templte.Replace(&line, ""))
217 m_wordList[tmplte].Add(FixTemplate(line));
218 else if (unt.Replace(&line, ""))
219 m_wordList[unit].Add(line);
220 else
221 wxLogMessage(privateList +
222 wxString::Format(_(": Can't interpret line: %s")), line.utf8_str());
223 }
224 }
225 priv.Close();
226 }
227 else
228 {
229 SuppressErrorDialogs logNull;
230 wxFileOutputStream output(privateList);
231 if(output.IsOk())
232 {
233 wxTextOutputStream text(output);
234 text << "# This file allows users to add their own symbols\n";
235 text << "# to wxMaxima's autocompletion feature.\n";
236 text << "# If a useful built-in symbol of Maxima is lacking\n";
237 text << "# in wxMaxima's autocompletion please inform the wxMaxima\n";
238 text << "# maintainers about this!\n";
239 text << "# \n";
240 text << "# The format of the entries in this file is:\n";
241 text << "# FUNCTION: myfunction\n";
242 text << "# OPTION: myvariable\n";
243 text << "# UNIT: myunit\n";
244 text << "# Template: mycommand(<expr>, <x>)";
245 text.Flush();
246 }
247 }
248 }
249 }
250
LoadSymbols_BackgroundTask()251 void AutoComplete::LoadSymbols_BackgroundTask()
252 {
253 #ifdef HAVE_OPENMP_TASKS
254 #pragma omp critical (AutocompleteFiles)
255 #endif
256 {
257 // Error dialogues need to be created by the foreground thread.
258 SuppressErrorDialogs suppressor;
259
260 // Prepare a list of all built-in loadable files of maxima.
261 {
262 GetMacFiles_includingSubdirs maximaLispIterator (m_builtInLoadFiles);
263 wxString sharedir = m_configuration->MaximaShareDir();
264 sharedir.Replace("\n","");
265 sharedir.Replace("\r","");
266 if(sharedir.IsEmpty())
267 wxLogMessage(_("Seems like the package with the maxima share files isn't installed."));
268 else
269 {
270 wxFileName shareDir(sharedir + "/");
271 shareDir.MakeAbsolute();
272 wxLogMessage(
273 wxString::Format(
274 _("Autocompletion: Scanning %s recursively for loadable lisp files."),
275 shareDir.GetFullPath().utf8_str()));
276 wxDir maximadir(shareDir.GetFullPath());
277 if(maximadir.IsOpened())
278 maximadir.Traverse(maximaLispIterator); //todo
279 }
280 GetMacFiles userLispIterator (m_builtInLoadFiles);
281 wxFileName userDir(Dirstructure::Get()->UserConfDir() + "/");
282 userDir.MakeAbsolute();
283 wxDir maximauserfilesdir(userDir.GetFullPath());
284 wxLogMessage(
285 wxString::Format(
286 _("Autocompletion: Scanning %s for loadable lisp files."),
287 userDir.GetFullPath().utf8_str()));
288 if(maximauserfilesdir.IsOpened())
289 maximauserfilesdir.Traverse(userLispIterator);
290 wxLogMessage(
291 wxString::Format(
292 _("Found %li loadable files."),
293 (unsigned long)m_builtInLoadFiles.GetCount()
294 )
295 );
296 }
297
298
299 // Prepare a list of all built-in demos of maxima.
300 {
301 wxFileName demoDir(m_configuration->MaximaShareDir() + "/");
302 demoDir.MakeAbsolute();
303 demoDir.RemoveLastDir();
304 GetDemoFiles_includingSubdirs maximaLispIterator (m_builtInDemoFiles);
305 wxLogMessage(
306 wxString::Format(
307 _("Autocompletion: Scanning %s for loadable demo files."),
308 demoDir.GetFullPath().utf8_str()));
309
310 wxDir maximadir(demoDir.GetFullPath());
311 if(maximadir.IsOpened())
312 maximadir.Traverse(maximaLispIterator);
313 }
314 wxLogMessage(
315 wxString::Format(
316 _("Found %li demo files."),
317 (unsigned long)m_builtInDemoFiles.GetCount()
318 )
319 );
320 m_builtInLoadFiles.Sort();
321 m_builtInDemoFiles.Sort();
322 }
323 }
324
UpdateDemoFiles(wxString partial,wxString maximaDir)325 void AutoComplete::UpdateDemoFiles(wxString partial, wxString maximaDir)
326 {
327 #ifdef HAVE_OPENMP_TASKS
328 #pragma omp critical (AutocompleteFiles)
329 #endif
330 {
331 // Remove the opening quote from the partial.
332 if(partial[0] == wxT('\"'))
333 partial = partial.Right(partial.Length()-1);
334
335 partial.Replace(wxFileName::GetPathSeparator(), "/");
336 int pos;
337 if ((pos = partial.Find(wxT('/'), true)) == wxNOT_FOUND)
338 partial = wxEmptyString;
339 else
340 partial = partial.Left(pos);
341 wxString prefix = partial + wxT("/");
342
343 // Determine if we need to add the path to maxima's current dir to the path in partial
344 if(!wxFileName(partial).IsAbsolute())
345 {
346 partial = maximaDir + wxFileName::GetPathSeparator() + partial;
347 partial.Replace(wxFileName::GetPathSeparator(), "/");
348 }
349
350 // Determine the name of the directory
351 if((partial != wxEmptyString) && wxDirExists(partial))
352 partial += "/";
353
354 // Remove all files from the maxima directory from the demo file list
355 ClearDemofileList();
356
357 // Add all files from the maxima directory to the demo file list
358 if(partial != wxT("//"))
359 {
360 GetDemoFiles userLispIterator(m_wordList[demofile], prefix);
361 wxDir demofilesdir(partial);
362 if(demofilesdir.IsOpened())
363 demofilesdir.Traverse(userLispIterator);
364 }
365 }
366 }
367
UpdateGeneralFiles(wxString partial,wxString maximaDir)368 void AutoComplete::UpdateGeneralFiles(wxString partial, wxString maximaDir)
369 {
370 #ifdef HAVE_OPENMP_TASKS
371 #pragma omp critical (AutocompleteFiles)
372 #endif
373 {
374 // Remove the opening quote from the partial.
375 if(partial[0] == wxT('\"'))
376 partial = partial.Right(partial.Length()-1);
377
378 partial.Replace(wxFileName::GetPathSeparator(), "/");
379 int pos;
380 if ((pos = partial.Find(wxT('/'), true)) == wxNOT_FOUND)
381 partial = wxEmptyString;
382 else
383 partial = partial.Left(pos);
384 wxString prefix = partial + wxT("/");
385
386 // Determine if we need to add the path to maxima's current dir to the path in partial
387 if(!wxFileName(partial).IsAbsolute())
388 {
389 partial = maximaDir + wxFileName::GetPathSeparator() + partial;
390 partial.Replace(wxFileName::GetPathSeparator(), "/");
391 }
392
393 // Determine the name of the directory
394 if((partial != wxEmptyString) && wxDirExists(partial))
395 partial += "/";
396
397 // Add all files from the maxima directory to the demo file list
398 if(partial != wxT("//"))
399 {
400 GetGeneralFiles fileIterator(m_wordList[generalfile], prefix);
401 wxDir generalfilesdir(partial);
402 if(generalfilesdir.IsOpened())
403 generalfilesdir.Traverse(fileIterator);
404 }
405 }
406 }
407
UpdateLoadFiles(wxString partial,wxString maximaDir)408 void AutoComplete::UpdateLoadFiles(wxString partial, wxString maximaDir)
409 {
410 #ifdef HAVE_OPENMP_TASKS
411 wxLogMessage(_("Starting a background task that scans for autocompletible file names."));
412 #pragma omp critical (AutocompleteFiles)
413 #endif
414 {
415 // Remove the opening quote from the partial.
416 if(partial[0] == wxT('\"'))
417 partial = partial.Right(partial.Length()-1);
418
419 partial.Replace(wxFileName::GetPathSeparator(), "/");
420 int pos;
421 if ((pos = partial.Find(wxT('/'), true)) == wxNOT_FOUND)
422 partial = wxEmptyString;
423 else
424 partial = partial.Left(pos);
425 wxString prefix = partial + wxT("/");
426
427 // Determine if we need to add the path to maxima's current dir to the path in partial
428 if(!wxFileName(partial).IsAbsolute())
429 {
430 partial = maximaDir + wxFileName::GetPathSeparator() + partial;
431 partial.Replace(wxFileName::GetPathSeparator(), "/");
432 }
433
434 // Determine the name of the directory
435 if((partial != wxEmptyString) && wxDirExists(partial))
436 partial += "/";
437
438 // Remove all files from the maxima directory from the load file list
439 m_wordList[loadfile] = m_builtInLoadFiles;
440
441 // Add all files from the maxima directory to the load file list
442 if(partial != wxT("//"))
443 {
444 GetMacFiles userLispIterator(m_wordList[loadfile], prefix);
445 wxDir loadfilesdir(partial);
446 if(loadfilesdir.IsOpened())
447 loadfilesdir.Traverse(userLispIterator);
448 }
449 }
450 }
451
452 /// Returns a string array with functions which start with partial.
CompleteSymbol(wxString partial,autoCompletionType type)453 wxArrayString AutoComplete::CompleteSymbol(wxString partial, autoCompletionType type)
454 {
455 wxArrayString completions;
456 wxArrayString perfectCompletions;
457
458 #ifdef HAVE_OPENMP_TASKS
459 #pragma omp critical (AutocompleteBuiltins)
460 #pragma omp critical (AutocompleteFiles)
461 #endif
462 {
463 if(
464 ((type == AutoComplete::demofile) || (type == AutoComplete::loadfile)) &&
465 (partial.EndsWith("\""))
466 )
467 partial = partial.Left(partial.Length() - 1);
468
469 wxASSERT_MSG((type >= command) && (type <= unit), _("Bug: Autocompletion requested for unknown type of item."));
470
471 if (type != tmplte)
472 {
473 for (size_t i = 0; i < m_wordList[type].GetCount(); i++)
474 {
475 if (m_wordList[type][i].StartsWith(partial) &&
476 completions.Index(m_wordList[type][i]) == wxNOT_FOUND)
477 completions.Add(m_wordList[type][i]);
478 }
479 }
480 else
481 {
482 for (size_t i = 0; i < m_wordList[type].GetCount(); i++)
483 {
484 wxString templ = m_wordList[type][i];
485 if (templ.StartsWith(partial))
486 {
487 if (completions.Index(templ) == wxNOT_FOUND)
488 completions.Add(templ);
489 if (templ.SubString(0, templ.Find(wxT("(")) - 1) == partial &&
490 perfectCompletions.Index(templ) == wxNOT_FOUND)
491 perfectCompletions.Add(templ);
492 }
493 }
494 }
495
496 // Add a list of words that were definied on the work sheet but that aren't
497 // defined as maxima commands or functions.
498 if (type == command)
499 {
500 WorksheetWords::const_iterator it;
501 for (it = m_worksheetWords.begin(); it != m_worksheetWords.end(); ++it)
502 {
503 if (it->first.StartsWith(partial))
504 {
505 if (completions.Index(it->first) == wxNOT_FOUND)
506 {
507 completions.Add(it->first);
508 }
509 }
510 }
511 }
512
513 completions.Sort();
514 }
515 if (perfectCompletions.Count() > 0)
516 return perfectCompletions;
517 return completions;
518 }
519
AddSymbol(wxString fun,autoCompletionType type)520 void AutoComplete::AddSymbol(wxString fun, autoCompletionType type)
521 {
522 #ifdef HAVE_OPENMP_TASKS
523 #pragma omp critical (AutocompleteBuiltins)
524 #endif
525 AddSymbol_nowait(fun, type);
526 }
527
AddSymbol_nowait(wxString fun,autoCompletionType type)528 void AutoComplete::AddSymbol_nowait(wxString fun, autoCompletionType type)
529 {
530 /// Check for function of template
531 if (fun.StartsWith(wxT("FUNCTION: ")))
532 {
533 fun = fun.Mid(10);
534 type = command;
535 }
536 else if (fun.StartsWith(wxT("TEMPLATE: ")))
537 {
538 fun = fun.Mid(10);
539 type = tmplte;
540 }
541 else if (fun.StartsWith(wxT("UNIT: ")))
542 {
543 fun = fun.Mid(6);
544 type = unit;
545 }
546
547 /// Add symbols
548 if ((type != tmplte) && m_wordList[type].Index(fun, true, true) == wxNOT_FOUND)
549 m_wordList[type].Add(fun);
550
551 /// Add templates - for given function and given argument count we
552 /// only add one template. We count the arguments by counting '<'
553 if (type == tmplte)
554 {
555 fun = FixTemplate(fun);
556 wxString funName = fun.SubString(0, fun.Find(wxT("(")));
557 long count = fun.Freq('<');
558 size_t i = 0;
559 for (i = 0; i < m_wordList[type].GetCount(); i++)
560 {
561 wxString t = m_wordList[type][i];
562 if (t.StartsWith(funName) && (t.Freq('<') == count))
563 break;
564 }
565 if (i == m_wordList[type].GetCount())
566 m_wordList[type].Add(fun);
567 }
568 }
569
570
FixTemplate(wxString templ)571 wxString AutoComplete::FixTemplate(wxString templ)
572 {
573 templ.Replace(wxT(" "), wxEmptyString);
574 templ.Replace(wxT(",..."), wxEmptyString);
575
576 /// This will change optional arguments
577 m_args.ReplaceAll(&templ, wxT("<[\\1]>"));
578
579 return templ;
580 }
581
582 wxRegEx AutoComplete::m_args("[<\\([^>]*\\)>]");
583