qsciapis.cpp 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997
  1. // This module implements the QsciAPIs class.
  2. //
  3. // Copyright (c) 2017 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. #include <stdlib.h>
  20. #include "Qsci/qsciapis.h"
  21. #include <QApplication>
  22. #include <QDataStream>
  23. #include <QDir>
  24. #include <QEvent>
  25. #include <QFile>
  26. #include <QLibraryInfo>
  27. #include <QMap>
  28. #include <QTextStream>
  29. #include <QThread>
  30. #include "Qsci/qscilexer.h"
  31. // The version number of the prepared API information format.
  32. const unsigned char PreparedDataFormatVersion = 0;
  33. // This class contains prepared API information.
  34. struct QsciAPIsPrepared
  35. {
  36. // The word dictionary is a map of individual words and a list of positions
  37. // each occurs in the sorted list of APIs. A position is a tuple of the
  38. // index into the list of APIs and the index into the particular API.
  39. QMap<QString, QsciAPIs::WordIndexList> wdict;
  40. // The case dictionary maps the case insensitive words to the form in which
  41. // they are to be used. It is only used if the language is case
  42. // insensitive.
  43. QMap<QString, QString> cdict;
  44. // The raw API information.
  45. QStringList raw_apis;
  46. QStringList apiWords(int api_idx, const QStringList &wseps,
  47. bool strip_image) const;
  48. static QString apiBaseName(const QString &api);
  49. };
  50. // Return a particular API entry as a list of words.
  51. QStringList QsciAPIsPrepared::apiWords(int api_idx, const QStringList &wseps,
  52. bool strip_image) const
  53. {
  54. QString base = apiBaseName(raw_apis[api_idx]);
  55. // Remove any embedded image reference if necessary.
  56. if (strip_image)
  57. {
  58. int tail = base.indexOf('?');
  59. if (tail >= 0)
  60. base.truncate(tail);
  61. }
  62. if (wseps.isEmpty())
  63. return QStringList(base);
  64. return base.split(wseps.first());
  65. }
  66. // Return the name of an API function, ie. without the arguments.
  67. QString QsciAPIsPrepared::apiBaseName(const QString &api)
  68. {
  69. QString base = api;
  70. int tail = base.indexOf('(');
  71. if (tail >= 0)
  72. base.truncate(tail);
  73. return base.simplified();
  74. }
  75. // The user event type that signals that the worker thread has started.
  76. const QEvent::Type WorkerStarted = static_cast<QEvent::Type>(QEvent::User + 1012);
  77. // The user event type that signals that the worker thread has finished.
  78. const QEvent::Type WorkerFinished = static_cast<QEvent::Type>(QEvent::User + 1013);
  79. // The user event type that signals that the worker thread has aborted.
  80. const QEvent::Type WorkerAborted = static_cast<QEvent::Type>(QEvent::User + 1014);
  81. // This class is the worker thread that post-processes the API set.
  82. class QsciAPIsWorker : public QThread
  83. {
  84. public:
  85. QsciAPIsWorker(QsciAPIs *apis);
  86. virtual ~QsciAPIsWorker();
  87. virtual void run();
  88. QsciAPIsPrepared *prepared;
  89. private:
  90. QsciAPIs *proxy;
  91. bool abort;
  92. };
  93. // The worker thread ctor.
  94. QsciAPIsWorker::QsciAPIsWorker(QsciAPIs *apis)
  95. : proxy(apis), prepared(0), abort(false)
  96. {
  97. }
  98. // The worker thread dtor.
  99. QsciAPIsWorker::~QsciAPIsWorker()
  100. {
  101. // Tell the thread to stop. There is no need to bother with a mutex.
  102. abort = true;
  103. // Wait for it to do so and hit it if it doesn't.
  104. if (!wait(500))
  105. terminate();
  106. if (prepared)
  107. delete prepared;
  108. }
  109. // The worker thread entry point.
  110. void QsciAPIsWorker::run()
  111. {
  112. // Sanity check.
  113. if (!prepared)
  114. return;
  115. // Tell the main thread we have started.
  116. QApplication::postEvent(proxy, new QEvent(WorkerStarted));
  117. // Sort the full list.
  118. prepared->raw_apis.sort();
  119. QStringList wseps = proxy->lexer()->autoCompletionWordSeparators();
  120. bool cs = proxy->lexer()->caseSensitive();
  121. // Split each entry into separate words but ignoring any arguments.
  122. for (int a = 0; a < prepared->raw_apis.count(); ++a)
  123. {
  124. // Check to see if we should stop.
  125. if (abort)
  126. break;
  127. QStringList words = prepared->apiWords(a, wseps, true);
  128. for (int w = 0; w < words.count(); ++w)
  129. {
  130. const QString &word = words[w];
  131. // Add the word's position to any existing list for this word.
  132. QsciAPIs::WordIndexList wil = prepared->wdict[word];
  133. // If the language is case insensitive and we haven't seen this
  134. // word before then save it in the case dictionary.
  135. if (!cs && wil.count() == 0)
  136. prepared->cdict[word.toUpper()] = word;
  137. wil.append(QsciAPIs::WordIndex(a, w));
  138. prepared->wdict[word] = wil;
  139. }
  140. }
  141. // Tell the main thread we have finished.
  142. QApplication::postEvent(proxy, new QEvent(abort ? WorkerAborted : WorkerFinished));
  143. }
  144. // The ctor.
  145. QsciAPIs::QsciAPIs(QsciLexer *lexer)
  146. : QsciAbstractAPIs(lexer), worker(0), origin_len(0)
  147. {
  148. prep = new QsciAPIsPrepared;
  149. }
  150. // The dtor.
  151. QsciAPIs::~QsciAPIs()
  152. {
  153. deleteWorker();
  154. delete prep;
  155. }
  156. // Delete the worker thread if there is one.
  157. void QsciAPIs::deleteWorker()
  158. {
  159. if (worker)
  160. {
  161. delete worker;
  162. worker = 0;
  163. }
  164. }
  165. //! Handle termination events from the worker thread.
  166. bool QsciAPIs::event(QEvent *e)
  167. {
  168. switch (e->type())
  169. {
  170. case WorkerStarted:
  171. emit apiPreparationStarted();
  172. return true;
  173. case WorkerAborted:
  174. deleteWorker();
  175. emit apiPreparationCancelled();
  176. return true;
  177. case WorkerFinished:
  178. delete prep;
  179. old_context.clear();
  180. prep = worker->prepared;
  181. worker->prepared = 0;
  182. deleteWorker();
  183. // Allow the raw API information to be modified.
  184. apis = prep->raw_apis;
  185. emit apiPreparationFinished();
  186. return true;
  187. }
  188. return QObject::event(e);
  189. }
  190. // Clear the current raw API entries.
  191. void QsciAPIs::clear()
  192. {
  193. apis.clear();
  194. }
  195. // Clear out all API information.
  196. bool QsciAPIs::load(const QString &filename)
  197. {
  198. QFile f(filename);
  199. if (!f.open(QIODevice::ReadOnly))
  200. return false;
  201. QTextStream ts(&f);
  202. for (;;)
  203. {
  204. QString line = ts.readLine();
  205. if (line.isEmpty())
  206. break;
  207. apis.append(line);
  208. }
  209. return true;
  210. }
  211. // Add a single API entry.
  212. void QsciAPIs::add(const QString &entry)
  213. {
  214. apis.append(entry);
  215. }
  216. // Remove a single API entry.
  217. void QsciAPIs::remove(const QString &entry)
  218. {
  219. int idx = apis.indexOf(entry);
  220. if (idx >= 0)
  221. apis.removeAt(idx);
  222. }
  223. // Position the "origin" cursor into the API entries according to the user
  224. // supplied context.
  225. QStringList QsciAPIs::positionOrigin(const QStringList &context, QString &path)
  226. {
  227. // Get the list of words and see if the context is the same as last time we
  228. // were called.
  229. QStringList new_context;
  230. bool same_context = (old_context.count() > 0 && old_context.count() < context.count());
  231. for (int i = 0; i < context.count(); ++i)
  232. {
  233. QString word = context[i];
  234. if (!lexer()->caseSensitive())
  235. word = word.toUpper();
  236. if (i < old_context.count() && old_context[i] != word)
  237. same_context = false;
  238. new_context << word;
  239. }
  240. // If the context has changed then reset the origin.
  241. if (!same_context)
  242. origin_len = 0;
  243. // If we have a current origin (ie. the user made a specific selection in
  244. // the current context) then adjust the origin to include the last complete
  245. // word as the user may have entered more parts of the name without using
  246. // auto-completion.
  247. if (origin_len > 0)
  248. {
  249. const QString wsep = lexer()->autoCompletionWordSeparators().first();
  250. int start_new = old_context.count();
  251. int end_new = new_context.count() - 1;
  252. if (start_new == end_new)
  253. {
  254. path = old_context.join(wsep);
  255. origin_len = path.length();
  256. }
  257. else
  258. {
  259. QString fixed = *origin;
  260. fixed.truncate(origin_len);
  261. path = fixed;
  262. while (start_new < end_new)
  263. {
  264. // Add this word to the current path.
  265. path.append(wsep);
  266. path.append(new_context[start_new]);
  267. origin_len = path.length();
  268. // Skip entries in the current origin that don't match the
  269. // path.
  270. while (origin != prep->raw_apis.end())
  271. {
  272. // See if the current origin has come to an end.
  273. if (!originStartsWith(fixed, wsep))
  274. origin = prep->raw_apis.end();
  275. else if (originStartsWith(path, wsep))
  276. break;
  277. else
  278. ++origin;
  279. }
  280. if (origin == prep->raw_apis.end())
  281. break;
  282. ++start_new;
  283. }
  284. }
  285. // Terminate the path.
  286. path.append(wsep);
  287. // If the new text wasn't recognised then reset the origin.
  288. if (origin == prep->raw_apis.end())
  289. origin_len = 0;
  290. }
  291. if (origin_len == 0)
  292. path.truncate(0);
  293. // Save the "committed" context for next time.
  294. old_context = new_context;
  295. old_context.removeLast();
  296. return new_context;
  297. }
  298. // Return true if the origin starts with the given path.
  299. bool QsciAPIs::originStartsWith(const QString &path, const QString &wsep)
  300. {
  301. const QString &orig = *origin;
  302. if (!orig.startsWith(path))
  303. return false;
  304. // Check that the path corresponds to the end of a word, ie. that what
  305. // follows in the origin is either a word separator or a (.
  306. QString tail = orig.mid(path.length());
  307. return (!tail.isEmpty() && (tail.startsWith(wsep) || tail.at(0) == '('));
  308. }
  309. // Add auto-completion words to an existing list.
  310. void QsciAPIs::updateAutoCompletionList(const QStringList &context,
  311. QStringList &list)
  312. {
  313. QString path;
  314. QStringList new_context = positionOrigin(context, path);
  315. if (origin_len > 0)
  316. {
  317. const QString wsep = lexer()->autoCompletionWordSeparators().first();
  318. QStringList::const_iterator it = origin;
  319. unambiguous_context = path;
  320. while (it != prep->raw_apis.end())
  321. {
  322. QString base = QsciAPIsPrepared::apiBaseName(*it);
  323. if (!base.startsWith(path))
  324. break;
  325. // Make sure we have something after the path.
  326. if (base != path)
  327. {
  328. // Get the word we are interested in (ie. the one after the
  329. // current origin in path).
  330. QString w = base.mid(origin_len + wsep.length()).split(wsep).first();
  331. // Append the space, we know the origin is unambiguous.
  332. w.append(' ');
  333. if (!list.contains(w))
  334. list << w;
  335. }
  336. ++it;
  337. }
  338. }
  339. else
  340. {
  341. // At the moment we assume we will add words from multiple contexts so
  342. // mark the unambiguous context as unknown.
  343. unambiguous_context = QString();
  344. bool unambig = true;
  345. QStringList with_context;
  346. if (new_context.last().isEmpty())
  347. lastCompleteWord(new_context[new_context.count() - 2], with_context, unambig);
  348. else
  349. lastPartialWord(new_context.last(), with_context, unambig);
  350. for (int i = 0; i < with_context.count(); ++i)
  351. {
  352. // Remove any unambigious context (allowing for a possible image
  353. // identifier).
  354. QString noc = with_context[i];
  355. if (unambig)
  356. {
  357. int op = noc.indexOf(QLatin1String(" ("));
  358. if (op >= 0)
  359. {
  360. int cl = noc.indexOf(QLatin1String(")"));
  361. if (cl > op)
  362. noc.remove(op, cl - op + 1);
  363. else
  364. noc.truncate(op);
  365. }
  366. }
  367. list << noc;
  368. }
  369. }
  370. }
  371. // Get the index list for a particular word if there is one.
  372. const QsciAPIs::WordIndexList *QsciAPIs::wordIndexOf(const QString &word) const
  373. {
  374. QString csword;
  375. // Indirect through the case dictionary if the language isn't case
  376. // sensitive.
  377. if (lexer()->caseSensitive())
  378. csword = word;
  379. else
  380. {
  381. csword = prep->cdict[word];
  382. if (csword.isEmpty())
  383. return 0;
  384. }
  385. // Get the possible API entries if any.
  386. const WordIndexList *wl = &prep->wdict[csword];
  387. if (wl->isEmpty())
  388. return 0;
  389. return wl;
  390. }
  391. // Add auto-completion words based on the last complete word entered.
  392. void QsciAPIs::lastCompleteWord(const QString &word, QStringList &with_context, bool &unambig)
  393. {
  394. // Get the possible API entries if any.
  395. const WordIndexList *wl = wordIndexOf(word);
  396. if (wl)
  397. addAPIEntries(*wl, true, with_context, unambig);
  398. }
  399. // Add auto-completion words based on the last partial word entered.
  400. void QsciAPIs::lastPartialWord(const QString &word, QStringList &with_context, bool &unambig)
  401. {
  402. if (lexer()->caseSensitive())
  403. {
  404. QMap<QString, WordIndexList>::const_iterator it = prep->wdict.lowerBound(word);
  405. while (it != prep->wdict.end())
  406. {
  407. if (!it.key().startsWith(word))
  408. break;
  409. addAPIEntries(it.value(), false, with_context, unambig);
  410. ++it;
  411. }
  412. }
  413. else
  414. {
  415. QMap<QString, QString>::const_iterator it = prep->cdict.lowerBound(word);
  416. while (it != prep->cdict.end())
  417. {
  418. if (!it.key().startsWith(word))
  419. break;
  420. addAPIEntries(prep->wdict[it.value()], false, with_context, unambig);
  421. ++it;
  422. }
  423. }
  424. }
  425. // Handle the selection of an entry in the auto-completion list.
  426. void QsciAPIs::autoCompletionSelected(const QString &selection)
  427. {
  428. // If the selection is an API (ie. it has a space separating the selected
  429. // word and the optional origin) then remember the origin.
  430. QStringList lst = selection.split(' ');
  431. if (lst.count() != 2)
  432. {
  433. origin_len = 0;
  434. return;
  435. }
  436. const QString &path = lst[1];
  437. QString owords;
  438. if (path.isEmpty())
  439. owords = unambiguous_context;
  440. else
  441. {
  442. // Check the parenthesis.
  443. if (!path.startsWith("(") || !path.endsWith(")"))
  444. {
  445. origin_len = 0;
  446. return;
  447. }
  448. // Remove the parenthesis.
  449. owords = path.mid(1, path.length() - 2);
  450. }
  451. origin = qLowerBound(prep->raw_apis, owords);
  452. /*
  453. * There is a bug somewhere, either in qLowerBound() or QList (or in GCC as
  454. * it seems to be Linux specific and the Qt code is the same on all
  455. * platforms) that the following line seems to fix. Note that it is
  456. * actually the call to detach() within begin() that is the important bit.
  457. */
  458. prep->raw_apis.begin();
  459. origin_len = owords.length();
  460. }
  461. // Add auto-completion words for a particular word (defined by where it appears
  462. // in the APIs) and depending on whether the word was complete (when it's
  463. // actually the next word in the API entry that is of interest) or not.
  464. void QsciAPIs::addAPIEntries(const WordIndexList &wl, bool complete,
  465. QStringList &with_context, bool &unambig)
  466. {
  467. QStringList wseps = lexer()->autoCompletionWordSeparators();
  468. for (int w = 0; w < wl.count(); ++w)
  469. {
  470. const WordIndex &wi = wl[w];
  471. QStringList api_words = prep->apiWords(wi.first, wseps, false);
  472. int idx = wi.second;
  473. if (complete)
  474. {
  475. // Skip if this is the last word.
  476. if (++idx >= api_words.count())
  477. continue;
  478. }
  479. QString api_word, org;
  480. if (idx == 0)
  481. {
  482. api_word = api_words[0] + ' ';
  483. org = QString::fromLatin1("");
  484. }
  485. else
  486. {
  487. QStringList orgl = api_words.mid(0, idx);
  488. org = orgl.join(wseps.first());
  489. // Add the context (allowing for a possible image identifier).
  490. QString w = api_words[idx];
  491. QString type;
  492. int type_idx = w.indexOf(QLatin1String("?"));
  493. if (type_idx >= 0)
  494. {
  495. type = w.mid(type_idx);
  496. w.truncate(type_idx);
  497. }
  498. api_word = QString("%1 (%2)%3").arg(w).arg(org).arg(type);
  499. }
  500. // If the origin is different to the context then the context is
  501. // ambiguous.
  502. if (unambig)
  503. {
  504. if (unambiguous_context.isNull())
  505. {
  506. unambiguous_context = org;
  507. }
  508. else if (unambiguous_context != org)
  509. {
  510. unambiguous_context.truncate(0);
  511. unambig = false;
  512. }
  513. }
  514. if (!with_context.contains(api_word))
  515. with_context.append(api_word);
  516. }
  517. }
  518. // Return the call tip for a function.
  519. QStringList QsciAPIs::callTips(const QStringList &context, int commas,
  520. QsciScintilla::CallTipsStyle style, QList<int> &shifts)
  521. {
  522. QString path;
  523. QStringList new_context = positionOrigin(context, path);
  524. QStringList wseps = lexer()->autoCompletionWordSeparators();
  525. QStringList cts;
  526. if (origin_len > 0)
  527. {
  528. // The path should have a trailing word separator.
  529. const QString &wsep = wseps.first();
  530. path.chop(wsep.length());
  531. QStringList::const_iterator it = origin;
  532. QString prev;
  533. // Work out the length of the context.
  534. QStringList strip = path.split(wsep);
  535. strip.removeLast();
  536. int ctstart = strip.join(wsep).length();
  537. if (ctstart)
  538. ctstart += wsep.length();
  539. int shift;
  540. if (style == QsciScintilla::CallTipsStyle::CallTipsContext)
  541. {
  542. shift = ctstart;
  543. ctstart = 0;
  544. }
  545. else
  546. shift = 0;
  547. // Make sure we only look at the functions we are interested in.
  548. path.append('(');
  549. while (it != prep->raw_apis.end() && (*it).startsWith(path))
  550. {
  551. QString w = (*it).mid(ctstart);
  552. if (w != prev && enoughCommas(w, commas))
  553. {
  554. shifts << shift;
  555. cts << w;
  556. prev = w;
  557. }
  558. ++it;
  559. }
  560. }
  561. else
  562. {
  563. const QString &fname = new_context[new_context.count() - 2];
  564. // Find everywhere the function name appears in the APIs.
  565. const WordIndexList *wil = wordIndexOf(fname);
  566. if (wil)
  567. for (int i = 0; i < wil->count(); ++i)
  568. {
  569. const WordIndex &wi = (*wil)[i];
  570. QStringList awords = prep->apiWords(wi.first, wseps, true);
  571. // Check the word is the function name and not part of any
  572. // context.
  573. if (wi.second != awords.count() - 1)
  574. continue;
  575. const QString &api = prep->raw_apis[wi.first];
  576. int tail = api.indexOf('(');
  577. if (tail < 0)
  578. continue;
  579. if (!enoughCommas(api, commas))
  580. continue;
  581. if (style == QsciScintilla::CallTipsStyle::CallTipsNoContext)
  582. {
  583. shifts << 0;
  584. cts << (fname + api.mid(tail));
  585. }
  586. else
  587. {
  588. shifts << tail - fname.length();
  589. // Remove any image type.
  590. int im_type = api.indexOf('?');
  591. if (im_type <= 0)
  592. cts << api;
  593. else
  594. cts << (api.left(im_type - 1) + api.mid(tail));
  595. }
  596. }
  597. }
  598. return cts;
  599. }
  600. // Return true if a string has enough commas in the argument list.
  601. bool QsciAPIs::enoughCommas(const QString &s, int commas)
  602. {
  603. int end = s.indexOf(')');
  604. if (end < 0)
  605. return false;
  606. QString w = s.left(end);
  607. return (w.count(',') >= commas);
  608. }
  609. // Ensure the list is ready.
  610. void QsciAPIs::prepare()
  611. {
  612. // Handle the trivial case.
  613. if (worker)
  614. return;
  615. QsciAPIsPrepared *new_apis = new QsciAPIsPrepared;
  616. new_apis->raw_apis = apis;
  617. worker = new QsciAPIsWorker(this);
  618. worker->prepared = new_apis;
  619. worker->start();
  620. }
  621. // Cancel any current preparation.
  622. void QsciAPIs::cancelPreparation()
  623. {
  624. deleteWorker();
  625. }
  626. // Check that a prepared API file exists.
  627. bool QsciAPIs::isPrepared(const QString &filename) const
  628. {
  629. QString pname = prepName(filename);
  630. if (pname.isEmpty())
  631. return false;
  632. QFileInfo fi(pname);
  633. return fi.exists();
  634. }
  635. // Load the prepared API information.
  636. bool QsciAPIs::loadPrepared(const QString &filename)
  637. {
  638. QString pname = prepName(filename);
  639. if (pname.isEmpty())
  640. return false;
  641. // Read the prepared data and decompress it.
  642. QFile pf(pname);
  643. if (!pf.open(QIODevice::ReadOnly))
  644. return false;
  645. QByteArray cpdata = pf.readAll();
  646. pf.close();
  647. if (cpdata.count() == 0)
  648. return false;
  649. QByteArray pdata = qUncompress(cpdata);
  650. // Extract the data.
  651. QDataStream pds(pdata);
  652. unsigned char vers;
  653. pds >> vers;
  654. if (vers > PreparedDataFormatVersion)
  655. return false;
  656. char *lex_name;
  657. pds >> lex_name;
  658. if (qstrcmp(lex_name, lexer()->lexer()) != 0)
  659. {
  660. delete[] lex_name;
  661. return false;
  662. }
  663. delete[] lex_name;
  664. prep->wdict.clear();
  665. pds >> prep->wdict;
  666. if (!lexer()->caseSensitive())
  667. {
  668. // Build up the case dictionary.
  669. prep->cdict.clear();
  670. QMap<QString, WordIndexList>::const_iterator it = prep->wdict.begin();
  671. while (it != prep->wdict.end())
  672. {
  673. prep->cdict[it.key().toUpper()] = it.key();
  674. ++it;
  675. }
  676. }
  677. prep->raw_apis.clear();
  678. pds >> prep->raw_apis;
  679. // Allow the raw API information to be modified.
  680. apis = prep->raw_apis;
  681. return true;
  682. }
  683. // Save the prepared API information.
  684. bool QsciAPIs::savePrepared(const QString &filename) const
  685. {
  686. QString pname = prepName(filename, true);
  687. if (pname.isEmpty())
  688. return false;
  689. // Write the prepared data to a memory buffer.
  690. QByteArray pdata;
  691. QDataStream pds(&pdata, QIODevice::WriteOnly);
  692. // Use a serialisation format supported by Qt v3.0 and later.
  693. pds.setVersion(QDataStream::Qt_3_0);
  694. pds << PreparedDataFormatVersion;
  695. pds << lexer()->lexer();
  696. pds << prep->wdict;
  697. pds << prep->raw_apis;
  698. // Compress the data and write it.
  699. QFile pf(pname);
  700. if (!pf.open(QIODevice::WriteOnly|QIODevice::Truncate))
  701. return false;
  702. if (pf.write(qCompress(pdata)) < 0)
  703. {
  704. pf.close();
  705. return false;
  706. }
  707. pf.close();
  708. return true;
  709. }
  710. // Return the name of the default prepared API file.
  711. QString QsciAPIs::defaultPreparedName() const
  712. {
  713. return prepName(QString());
  714. }
  715. // Return the name of a prepared API file.
  716. QString QsciAPIs::prepName(const QString &filename, bool mkpath) const
  717. {
  718. // Handle the tivial case.
  719. if (!filename.isEmpty())
  720. return filename;
  721. QString pdname;
  722. char *qsci = getenv("QSCIDIR");
  723. if (qsci)
  724. pdname = qsci;
  725. else
  726. {
  727. static const char *qsci_dir = ".qsci";
  728. QDir pd = QDir::home();
  729. if (mkpath && !pd.exists(qsci_dir) && !pd.mkdir(qsci_dir))
  730. return QString();
  731. pdname = pd.filePath(qsci_dir);
  732. }
  733. return QString("%1/%2.pap").arg(pdname).arg(lexer()->lexer());
  734. }
  735. // Return installed API files.
  736. QStringList QsciAPIs::installedAPIFiles() const
  737. {
  738. QString qtdir = QLibraryInfo::location(QLibraryInfo::DataPath);
  739. QDir apidir = QDir(QString("%1/qsci/api/%2").arg(qtdir).arg(lexer()->lexer()));
  740. QStringList filenames;
  741. QStringList filters;
  742. filters << "*.api";
  743. QFileInfoList flist = apidir.entryInfoList(filters, QDir::Files, QDir::IgnoreCase);
  744. foreach (QFileInfo fi, flist)
  745. filenames << fi.absoluteFilePath();
  746. return filenames;
  747. }