1 // This module implements the QsciAPIs class.
2 //
3 // Copyright (c) 2021 Riverbank Computing Limited <info@riverbankcomputing.com>
4 //
5 // This file is part of QScintilla.
6 //
7 // This file may be used under the terms of the GNU General Public License
8 // version 3.0 as published by the Free Software Foundation and appearing in
9 // the file LICENSE included in the packaging of this file. Please review the
10 // following information to ensure the GNU General Public License version 3.0
11 // requirements will be met: http://www.gnu.org/copyleft/gpl.html.
12 //
13 // If you do not wish to use this file under the terms of the GPL version 3.0
14 // then you may purchase a commercial license. For more information contact
15 // info@riverbankcomputing.com.
16 //
17 // This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
18 // WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
19
20
21 #include <stdlib.h>
22
23 #include <algorithm>
24
25 #include "Qsci/qsciapis.h"
26
27 #include <QApplication>
28 #include <QDataStream>
29 #include <QDir>
30 #include <QEvent>
31 #include <QFile>
32 #include <QLibraryInfo>
33 #include <QMap>
34 #include <QTextStream>
35 #include <QThread>
36
37 #include "Qsci/qscilexer.h"
38
39
40
41 // The version number of the prepared API information format.
42 const unsigned char PreparedDataFormatVersion = 0;
43
44
45 // This class contains prepared API information.
46 struct QsciAPIsPrepared
47 {
48 // The word dictionary is a map of individual words and a list of positions
49 // each occurs in the sorted list of APIs. A position is a tuple of the
50 // index into the list of APIs and the index into the particular API.
51 QMap<QString, QsciAPIs::WordIndexList> wdict;
52
53 // The case dictionary maps the case insensitive words to the form in which
54 // they are to be used. It is only used if the language is case
55 // insensitive.
56 QMap<QString, QString> cdict;
57
58 // The raw API information.
59 QStringList raw_apis;
60
61 QStringList apiWords(int api_idx, const QStringList &wseps,
62 bool strip_image) const;
63 static QString apiBaseName(const QString &api);
64 };
65
66
67 // Return a particular API entry as a list of words.
apiWords(int api_idx,const QStringList & wseps,bool strip_image) const68 QStringList QsciAPIsPrepared::apiWords(int api_idx, const QStringList &wseps,
69 bool strip_image) const
70 {
71 QString base = apiBaseName(raw_apis[api_idx]);
72
73 // Remove any embedded image reference if necessary.
74 if (strip_image)
75 {
76 int tail = base.indexOf('?');
77
78 if (tail >= 0)
79 base.truncate(tail);
80 }
81
82 if (wseps.isEmpty())
83 return QStringList(base);
84
85 return base.split(wseps.first());
86 }
87
88
89 // Return the name of an API function, ie. without the arguments.
apiBaseName(const QString & api)90 QString QsciAPIsPrepared::apiBaseName(const QString &api)
91 {
92 QString base = api;
93 int tail = base.indexOf('(');
94
95 if (tail >= 0)
96 base.truncate(tail);
97
98 return base.simplified();
99 }
100
101
102 // The user event type that signals that the worker thread has started.
103 const QEvent::Type WorkerStarted = static_cast<QEvent::Type>(QEvent::User + 1012);
104
105
106 // The user event type that signals that the worker thread has finished.
107 const QEvent::Type WorkerFinished = static_cast<QEvent::Type>(QEvent::User + 1013);
108
109
110 // The user event type that signals that the worker thread has aborted.
111 const QEvent::Type WorkerAborted = static_cast<QEvent::Type>(QEvent::User + 1014);
112
113
114 // This class is the worker thread that post-processes the API set.
115 class QsciAPIsWorker : public QThread
116 {
117 public:
118 QsciAPIsWorker(QsciAPIs *apis);
119 virtual ~QsciAPIsWorker();
120
121 virtual void run();
122
123 QsciAPIsPrepared *prepared;
124
125 private:
126 QsciAPIs *proxy;
127 bool abort;
128 };
129
130
131 // The worker thread ctor.
QsciAPIsWorker(QsciAPIs * apis)132 QsciAPIsWorker::QsciAPIsWorker(QsciAPIs *apis)
133 : prepared(0), proxy(apis), abort(false)
134 {
135 }
136
137
138 // The worker thread dtor.
~QsciAPIsWorker()139 QsciAPIsWorker::~QsciAPIsWorker()
140 {
141 // Tell the thread to stop. There is no need to bother with a mutex.
142 abort = true;
143
144 // Wait for it to do so and hit it if it doesn't.
145 if (!wait(500))
146 terminate();
147
148 if (prepared)
149 delete prepared;
150 }
151
152
153 // The worker thread entry point.
run()154 void QsciAPIsWorker::run()
155 {
156 // Sanity check.
157 if (!prepared)
158 return;
159
160 // Tell the main thread we have started.
161 QApplication::postEvent(proxy, new QEvent(WorkerStarted));
162
163 // Sort the full list.
164 prepared->raw_apis.sort();
165
166 QStringList wseps = proxy->lexer()->autoCompletionWordSeparators();
167 bool cs = proxy->lexer()->caseSensitive();
168
169 // Split each entry into separate words but ignoring any arguments.
170 for (int a = 0; a < prepared->raw_apis.count(); ++a)
171 {
172 // Check to see if we should stop.
173 if (abort)
174 break;
175
176 QStringList words = prepared->apiWords(a, wseps, true);
177
178 for (int w = 0; w < words.count(); ++w)
179 {
180 const QString &word = words[w];
181
182 // Add the word's position to any existing list for this word.
183 QsciAPIs::WordIndexList wil = prepared->wdict[word];
184
185 // If the language is case insensitive and we haven't seen this
186 // word before then save it in the case dictionary.
187 if (!cs && wil.count() == 0)
188 prepared->cdict[word.toUpper()] = word;
189
190 wil.append(QsciAPIs::WordIndex(a, w));
191 prepared->wdict[word] = wil;
192 }
193 }
194
195 // Tell the main thread we have finished.
196 QApplication::postEvent(proxy, new QEvent(abort ? WorkerAborted : WorkerFinished));
197 }
198
199
200 // The ctor.
QsciAPIs(QsciLexer * lexer)201 QsciAPIs::QsciAPIs(QsciLexer *lexer)
202 : QsciAbstractAPIs(lexer), worker(0), origin_len(0)
203 {
204 prep = new QsciAPIsPrepared;
205 }
206
207
208 // The dtor.
~QsciAPIs()209 QsciAPIs::~QsciAPIs()
210 {
211 deleteWorker();
212 delete prep;
213 }
214
215
216 // Delete the worker thread if there is one.
deleteWorker()217 void QsciAPIs::deleteWorker()
218 {
219 if (worker)
220 {
221 delete worker;
222 worker = 0;
223 }
224 }
225
226
227 //! Handle termination events from the worker thread.
event(QEvent * e)228 bool QsciAPIs::event(QEvent *e)
229 {
230 switch (e->type())
231 {
232 case WorkerStarted:
233 emit apiPreparationStarted();
234 return true;
235
236 case WorkerAborted:
237 deleteWorker();
238 emit apiPreparationCancelled();
239 return true;
240
241 case WorkerFinished:
242 delete prep;
243 old_context.clear();
244
245 prep = worker->prepared;
246 worker->prepared = 0;
247 deleteWorker();
248
249 // Allow the raw API information to be modified.
250 apis = prep->raw_apis;
251
252 emit apiPreparationFinished();
253
254 return true;
255
256 default:
257 break;
258 }
259
260 return QObject::event(e);
261 }
262
263
264 // Clear the current raw API entries.
clear()265 void QsciAPIs::clear()
266 {
267 apis.clear();
268 }
269
270
271 // Clear out all API information.
load(const QString & filename)272 bool QsciAPIs::load(const QString &filename)
273 {
274 QFile f(filename);
275
276 if (!f.open(QIODevice::ReadOnly))
277 return false;
278
279 QTextStream ts(&f);
280
281 for (;;)
282 {
283 QString line = ts.readLine();
284
285 if (line.isEmpty())
286 break;
287
288 apis.append(line);
289 }
290
291 return true;
292 }
293
294
295 // Add a single API entry.
add(const QString & entry)296 void QsciAPIs::add(const QString &entry)
297 {
298 apis.append(entry);
299 }
300
301
302 // Remove a single API entry.
remove(const QString & entry)303 void QsciAPIs::remove(const QString &entry)
304 {
305 int idx = apis.indexOf(entry);
306
307 if (idx >= 0)
308 apis.removeAt(idx);
309 }
310
311
312 // Position the "origin" cursor into the API entries according to the user
313 // supplied context.
positionOrigin(const QStringList & context,QString & path)314 QStringList QsciAPIs::positionOrigin(const QStringList &context, QString &path)
315 {
316 // Get the list of words and see if the context is the same as last time we
317 // were called.
318 QStringList new_context;
319 bool same_context = (old_context.count() > 0 && old_context.count() < context.count());
320
321 for (int i = 0; i < context.count(); ++i)
322 {
323 QString word = context[i];
324
325 if (!lexer()->caseSensitive())
326 word = word.toUpper();
327
328 if (i < old_context.count() && old_context[i] != word)
329 same_context = false;
330
331 new_context << word;
332 }
333
334 // If the context has changed then reset the origin.
335 if (!same_context)
336 origin_len = 0;
337
338 // If we have a current origin (ie. the user made a specific selection in
339 // the current context) then adjust the origin to include the last complete
340 // word as the user may have entered more parts of the name without using
341 // auto-completion.
342 if (origin_len > 0)
343 {
344 const QString wsep = lexer()->autoCompletionWordSeparators().first();
345
346 int start_new = old_context.count();
347 int end_new = new_context.count() - 1;
348
349 if (start_new == end_new)
350 {
351 path = old_context.join(wsep);
352 origin_len = path.length();
353 }
354 else
355 {
356 QString fixed = *origin;
357 fixed.truncate(origin_len);
358
359 path = fixed;
360
361 while (start_new < end_new)
362 {
363 // Add this word to the current path.
364 path.append(wsep);
365 path.append(new_context[start_new]);
366 origin_len = path.length();
367
368 // Skip entries in the current origin that don't match the
369 // path.
370 while (origin != prep->raw_apis.end())
371 {
372 // See if the current origin has come to an end.
373 if (!originStartsWith(fixed, wsep))
374 origin = prep->raw_apis.end();
375 else if (originStartsWith(path, wsep))
376 break;
377 else
378 ++origin;
379 }
380
381 if (origin == prep->raw_apis.end())
382 break;
383
384 ++start_new;
385 }
386 }
387
388 // Terminate the path.
389 path.append(wsep);
390
391 // If the new text wasn't recognised then reset the origin.
392 if (origin == prep->raw_apis.end())
393 origin_len = 0;
394 }
395
396 if (origin_len == 0)
397 path.truncate(0);
398
399 // Save the "committed" context for next time.
400 old_context = new_context;
401 old_context.removeLast();
402
403 return new_context;
404 }
405
406
407 // Return true if the origin starts with the given path.
originStartsWith(const QString & path,const QString & wsep)408 bool QsciAPIs::originStartsWith(const QString &path, const QString &wsep)
409 {
410 const QString &orig = *origin;
411
412 if (!orig.startsWith(path))
413 return false;
414
415 // Check that the path corresponds to the end of a word, ie. that what
416 // follows in the origin is either a word separator or a (.
417 QString tail = orig.mid(path.length());
418
419 return (!tail.isEmpty() && (tail.startsWith(wsep) || tail.at(0) == '('));
420 }
421
422
423 // Add auto-completion words to an existing list.
updateAutoCompletionList(const QStringList & context,QStringList & list)424 void QsciAPIs::updateAutoCompletionList(const QStringList &context,
425 QStringList &list)
426 {
427 QString path;
428 QStringList new_context = positionOrigin(context, path);
429
430 if (origin_len > 0)
431 {
432 const QString wsep = lexer()->autoCompletionWordSeparators().first();
433 QStringList::const_iterator it = origin;
434
435 unambiguous_context = path;
436
437 while (it != prep->raw_apis.end())
438 {
439 QString base = QsciAPIsPrepared::apiBaseName(*it);
440
441 if (!base.startsWith(path))
442 break;
443
444 // Make sure we have something after the path.
445 if (base != path)
446 {
447 // Get the word we are interested in (ie. the one after the
448 // current origin in path).
449 QString w = base.mid(origin_len + wsep.length()).split(wsep).first();
450
451 // Append the space, we know the origin is unambiguous.
452 w.append(' ');
453
454 if (!list.contains(w))
455 list << w;
456 }
457
458 ++it;
459 }
460 }
461 else
462 {
463 // At the moment we assume we will add words from multiple contexts so
464 // mark the unambiguous context as unknown.
465 unambiguous_context = QString();
466
467 bool unambig = true;
468 QStringList with_context;
469
470 if (new_context.last().isEmpty())
471 lastCompleteWord(new_context[new_context.count() - 2], with_context, unambig);
472 else
473 lastPartialWord(new_context.last(), with_context, unambig);
474
475 for (int i = 0; i < with_context.count(); ++i)
476 {
477 // Remove any unambigious context (allowing for a possible image
478 // identifier).
479 QString noc = with_context[i];
480
481 if (unambig)
482 {
483 int op = noc.indexOf(QLatin1String(" ("));
484
485 if (op >= 0)
486 {
487 int cl = noc.indexOf(QLatin1String(")"));
488
489 if (cl > op)
490 noc.remove(op, cl - op + 1);
491 else
492 noc.truncate(op);
493 }
494 }
495
496 list << noc;
497 }
498 }
499 }
500
501
502 // Get the index list for a particular word if there is one.
wordIndexOf(const QString & word) const503 const QsciAPIs::WordIndexList *QsciAPIs::wordIndexOf(const QString &word) const
504 {
505 QString csword;
506
507 // Indirect through the case dictionary if the language isn't case
508 // sensitive.
509 if (lexer()->caseSensitive())
510 csword = word;
511 else
512 {
513 csword = prep->cdict[word];
514
515 if (csword.isEmpty())
516 return 0;
517 }
518
519 // Get the possible API entries if any.
520 const WordIndexList *wl = &prep->wdict[csword];
521
522 if (wl->isEmpty())
523 return 0;
524
525 return wl;
526 }
527
528
529 // Add auto-completion words based on the last complete word entered.
lastCompleteWord(const QString & word,QStringList & with_context,bool & unambig)530 void QsciAPIs::lastCompleteWord(const QString &word, QStringList &with_context, bool &unambig)
531 {
532 // Get the possible API entries if any.
533 const WordIndexList *wl = wordIndexOf(word);
534
535 if (wl)
536 addAPIEntries(*wl, true, with_context, unambig);
537 }
538
539
540 // Add auto-completion words based on the last partial word entered.
lastPartialWord(const QString & word,QStringList & with_context,bool & unambig)541 void QsciAPIs::lastPartialWord(const QString &word, QStringList &with_context, bool &unambig)
542 {
543 if (lexer()->caseSensitive())
544 {
545 QMap<QString, WordIndexList>::const_iterator it = prep->wdict.lowerBound(word);
546
547 while (it != prep->wdict.end())
548 {
549 if (!it.key().startsWith(word))
550 break;
551
552 addAPIEntries(it.value(), false, with_context, unambig);
553
554 ++it;
555 }
556 }
557 else
558 {
559 QMap<QString, QString>::const_iterator it = prep->cdict.lowerBound(word);
560
561 while (it != prep->cdict.end())
562 {
563 if (!it.key().startsWith(word))
564 break;
565
566 addAPIEntries(prep->wdict[it.value()], false, with_context, unambig);
567
568 ++it;
569 }
570 }
571 }
572
573
574 // Handle the selection of an entry in the auto-completion list.
autoCompletionSelected(const QString & selection)575 void QsciAPIs::autoCompletionSelected(const QString &selection)
576 {
577 // If the selection is an API (ie. it has a space separating the selected
578 // word and the optional origin) then remember the origin.
579 QStringList lst = selection.split(' ');
580
581 if (lst.count() != 2)
582 {
583 origin_len = 0;
584 return;
585 }
586
587 const QString &path = lst[1];
588 QString owords;
589
590 if (path.isEmpty())
591 owords = unambiguous_context;
592 else
593 {
594 // Check the parenthesis.
595 if (!path.startsWith("(") || !path.endsWith(")"))
596 {
597 origin_len = 0;
598 return;
599 }
600
601 // Remove the parenthesis.
602 owords = path.mid(1, path.length() - 2);
603 }
604
605 origin = std::lower_bound(prep->raw_apis.begin(), prep->raw_apis.end(),
606 owords);
607 origin_len = owords.length();
608 }
609
610
611 // Add auto-completion words for a particular word (defined by where it appears
612 // in the APIs) and depending on whether the word was complete (when it's
613 // actually the next word in the API entry that is of interest) or not.
addAPIEntries(const WordIndexList & wl,bool complete,QStringList & with_context,bool & unambig)614 void QsciAPIs::addAPIEntries(const WordIndexList &wl, bool complete,
615 QStringList &with_context, bool &unambig)
616 {
617 QStringList wseps = lexer()->autoCompletionWordSeparators();
618
619 for (int w = 0; w < wl.count(); ++w)
620 {
621 const WordIndex &wi = wl[w];
622
623 QStringList api_words = prep->apiWords(wi.first, wseps, false);
624
625 int idx = wi.second;
626
627 if (complete)
628 {
629 // Skip if this is the last word.
630 if (++idx >= api_words.count())
631 continue;
632 }
633
634 QString api_word, org;
635
636 if (idx == 0)
637 {
638 api_word = api_words[0] + ' ';
639 org = QString::fromLatin1("");
640 }
641 else
642 {
643 QStringList orgl = api_words.mid(0, idx);
644 org = orgl.join(wseps.first());
645
646 // Add the context (allowing for a possible image identifier).
647 QString w = api_words[idx];
648 QString type;
649 int type_idx = w.indexOf(QLatin1String("?"));
650
651 if (type_idx >= 0)
652 {
653 type = w.mid(type_idx);
654 w.truncate(type_idx);
655 }
656
657 api_word = QString("%1 (%2)%3").arg(w).arg(org).arg(type);
658 }
659
660 // If the origin is different to the context then the context is
661 // ambiguous.
662 if (unambig)
663 {
664 if (unambiguous_context.isNull())
665 {
666 unambiguous_context = org;
667 }
668 else if (unambiguous_context != org)
669 {
670 unambiguous_context.truncate(0);
671 unambig = false;
672 }
673 }
674
675 if (!with_context.contains(api_word))
676 with_context.append(api_word);
677 }
678 }
679
680
681 // Return the call tip for a function.
callTips(const QStringList & context,int commas,QsciScintilla::CallTipsStyle style,QList<int> & shifts)682 QStringList QsciAPIs::callTips(const QStringList &context, int commas,
683 QsciScintilla::CallTipsStyle style, QList<int> &shifts)
684 {
685 QString path;
686 QStringList new_context = positionOrigin(context, path);
687 QStringList wseps = lexer()->autoCompletionWordSeparators();
688 QStringList cts;
689
690 if (origin_len > 0)
691 {
692 // The path should have a trailing word separator.
693 const QString &wsep = wseps.first();
694 path.chop(wsep.length());
695
696 QStringList::const_iterator it = origin;
697 QString prev;
698
699 // Work out the length of the context.
700 QStringList strip = path.split(wsep);
701 strip.removeLast();
702 int ctstart = strip.join(wsep).length();
703
704 if (ctstart)
705 ctstart += wsep.length();
706
707 int shift;
708
709 if (style == QsciScintilla::CallTipsContext)
710 {
711 shift = ctstart;
712 ctstart = 0;
713 }
714 else
715 shift = 0;
716
717 // Make sure we only look at the functions we are interested in.
718 path.append('(');
719
720 while (it != prep->raw_apis.end() && (*it).startsWith(path))
721 {
722 QString w = (*it).mid(ctstart);
723
724 if (w != prev && enoughCommas(w, commas))
725 {
726 shifts << shift;
727 cts << w;
728 prev = w;
729 }
730
731 ++it;
732 }
733 }
734 else
735 {
736 const QString &fname = new_context[new_context.count() - 2];
737
738 // Find everywhere the function name appears in the APIs.
739 const WordIndexList *wil = wordIndexOf(fname);
740
741 if (wil)
742 for (int i = 0; i < wil->count(); ++i)
743 {
744 const WordIndex &wi = (*wil)[i];
745 QStringList awords = prep->apiWords(wi.first, wseps, true);
746
747 // Check the word is the function name and not part of any
748 // context.
749 if (wi.second != awords.count() - 1)
750 continue;
751
752 const QString &api = prep->raw_apis[wi.first];
753
754 int tail = api.indexOf('(');
755
756 if (tail < 0)
757 continue;
758
759 if (!enoughCommas(api, commas))
760 continue;
761
762 if (style == QsciScintilla::CallTipsNoContext)
763 {
764 shifts << 0;
765 cts << (fname + api.mid(tail));
766 }
767 else
768 {
769 shifts << tail - fname.length();
770
771 // Remove any image type.
772 int im_type = api.indexOf('?');
773
774 if (im_type <= 0)
775 cts << api;
776 else
777 cts << (api.left(im_type - 1) + api.mid(tail));
778 }
779 }
780 }
781
782 return cts;
783 }
784
785
786 // Return true if a string has enough commas in the argument list.
enoughCommas(const QString & s,int commas)787 bool QsciAPIs::enoughCommas(const QString &s, int commas)
788 {
789 int end = s.indexOf(')');
790
791 if (end < 0)
792 return false;
793
794 QString w = s.left(end);
795
796 return (w.count(',') >= commas);
797 }
798
799
800 // Ensure the list is ready.
prepare()801 void QsciAPIs::prepare()
802 {
803 // Handle the trivial case.
804 if (worker)
805 return;
806
807 QsciAPIsPrepared *new_apis = new QsciAPIsPrepared;
808 new_apis->raw_apis = apis;
809
810 worker = new QsciAPIsWorker(this);
811 worker->prepared = new_apis;
812 worker->start();
813 }
814
815
816 // Cancel any current preparation.
cancelPreparation()817 void QsciAPIs::cancelPreparation()
818 {
819 deleteWorker();
820 }
821
822
823 // Check that a prepared API file exists.
isPrepared(const QString & filename) const824 bool QsciAPIs::isPrepared(const QString &filename) const
825 {
826 QString pname = prepName(filename);
827
828 if (pname.isEmpty())
829 return false;
830
831 QFileInfo fi(pname);
832
833 return fi.exists();
834 }
835
836
837 // Load the prepared API information.
loadPrepared(const QString & filename)838 bool QsciAPIs::loadPrepared(const QString &filename)
839 {
840 QString pname = prepName(filename);
841
842 if (pname.isEmpty())
843 return false;
844
845 // Read the prepared data and decompress it.
846 QFile pf(pname);
847
848 if (!pf.open(QIODevice::ReadOnly))
849 return false;
850
851 QByteArray cpdata = pf.readAll();
852
853 pf.close();
854
855 if (cpdata.count() == 0)
856 return false;
857
858 QByteArray pdata = qUncompress(cpdata);
859
860 // Extract the data.
861 QDataStream pds(pdata);
862
863 unsigned char vers;
864 pds >> vers;
865
866 if (vers > PreparedDataFormatVersion)
867 return false;
868
869 char *lex_name;
870 pds >> lex_name;
871
872 if (qstrcmp(lex_name, lexer()->lexer()) != 0)
873 {
874 delete[] lex_name;
875 return false;
876 }
877
878 delete[] lex_name;
879
880 prep->wdict.clear();
881 pds >> prep->wdict;
882
883 if (!lexer()->caseSensitive())
884 {
885 // Build up the case dictionary.
886 prep->cdict.clear();
887
888 QMap<QString, WordIndexList>::const_iterator it = prep->wdict.begin();
889
890 while (it != prep->wdict.end())
891 {
892 prep->cdict[it.key().toUpper()] = it.key();
893 ++it;
894 }
895 }
896
897 prep->raw_apis.clear();
898 pds >> prep->raw_apis;
899
900 // Allow the raw API information to be modified.
901 apis = prep->raw_apis;
902
903 return true;
904 }
905
906
907 // Save the prepared API information.
savePrepared(const QString & filename) const908 bool QsciAPIs::savePrepared(const QString &filename) const
909 {
910 QString pname = prepName(filename, true);
911
912 if (pname.isEmpty())
913 return false;
914
915 // Write the prepared data to a memory buffer.
916 QByteArray pdata;
917 QDataStream pds(&pdata, QIODevice::WriteOnly);
918
919 // Use a serialisation format supported by Qt v3.0 and later.
920 pds.setVersion(QDataStream::Qt_3_0);
921 pds << PreparedDataFormatVersion;
922 pds << lexer()->lexer();
923 pds << prep->wdict;
924 pds << prep->raw_apis;
925
926 // Compress the data and write it.
927 QFile pf(pname);
928
929 if (!pf.open(QIODevice::WriteOnly|QIODevice::Truncate))
930 return false;
931
932 if (pf.write(qCompress(pdata)) < 0)
933 {
934 pf.close();
935 return false;
936 }
937
938 pf.close();
939 return true;
940 }
941
942
943 // Return the name of the default prepared API file.
defaultPreparedName() const944 QString QsciAPIs::defaultPreparedName() const
945 {
946 return prepName(QString());
947 }
948
949
950 // Return the name of a prepared API file.
prepName(const QString & filename,bool mkpath) const951 QString QsciAPIs::prepName(const QString &filename, bool mkpath) const
952 {
953 // Handle the tivial case.
954 if (!filename.isEmpty())
955 return filename;
956
957 QString pdname;
958 char *qsci = getenv("QSCIDIR");
959
960 if (qsci)
961 pdname = qsci;
962 else
963 {
964 static const char *qsci_dir = ".qsci";
965
966 QDir pd = QDir::home();
967
968 if (mkpath && !pd.exists(qsci_dir) && !pd.mkdir(qsci_dir))
969 return QString();
970
971 pdname = pd.filePath(qsci_dir);
972 }
973
974 return QString("%1/%2.pap").arg(pdname).arg(lexer()->lexer());
975 }
976
977
978 // Return installed API files.
installedAPIFiles() const979 QStringList QsciAPIs::installedAPIFiles() const
980 {
981 QString qtdir = QLibraryInfo::location(QLibraryInfo::DataPath);
982
983 QDir apidir = QDir(QString("%1/qsci/api/%2").arg(qtdir).arg(lexer()->lexer()));
984 QStringList filenames;
985
986 QStringList filters;
987 filters << "*.api";
988
989 QFileInfoList flist = apidir.entryInfoList(filters, QDir::Files, QDir::IgnoreCase);
990
991 foreach (QFileInfo fi, flist)
992 filenames << fi.absoluteFilePath();
993
994 return filenames;
995 }
996